7 min read

Storing Secrets Securely and Encrypted using Git-Crypt

Transparently encrypt and decrypt your secrets like .env files and others during git push and clone via git-crypt.
Storing Secrets Securely and Encrypted using Git-Crypt
Photo by Dima Pechurin / Unsplash


In the fast-paced world of software development, where collaboration and version control are paramount, the management of secrets is a topic that often takes a backseat. However, the importance of secrets management cannot be overstated. Whether you're using Gitea, GitHub or GitLab to store your code, one common challenge developers face is the handling of sensitive information, such as API keys, passwords and configuration settings. Some of them e.g. stored in a .env or other configuration files.

It's a delicate balancing act between not pushing these secrets onto the repository, which could lead to a lack of backups and availability, and pushing them into the repository, risking exposure to unauthorized persons. Imagine accidentally sharing your database password or an API key with the world; a developer's worst nightmare. This dilemma often forces developers to choose between security and convenience, with no easy solution in sight. Alternatively, using a secret management platform like Hashicorp Vault or others.

In this blog post, we will outline how to achieve an easy way of securely storing secrets in git repositories. For example as selfhoster, with many docker-compose.yml files and .env files with secrets in it.

The solution is called git-crypt.

What is Git-Crypt?

Git-crypt, available on GitHub, is a powerful tool that enables transparent encryption and decryption of files within a Git repository.

Transparent means that you can clone and push your code the normal way as usual, as if git-crypt would not be active. Under the hood though, git-crypt handles the encryption and decryption of your sensitive repository files automagically.

You just have to initialize git-crypt on your git repository and be in possession of the generated key for encryption and decryption later on.

How does it work?

Here is how it works in a high-level overview:

  • You initialize git-crypt on your git repository. Git-crypt will then create your unique key for encrypting and decrypting files. This key should be exported and backed up securely. The key enables access to your sensitive files later on.
  • When you commit files that you want to protect, git-crypt automatically encrypts them using the aforementioned key. Which files are encrypted by git-crypt is defined in a .gitattributes file. Note that git-crypt does not encrypt file names, commit messages, symlink targets, gitlinks, or other metadata. Files encrypted with git-crypt are not compressible. Even the smallest change to an encrypted file requires git to store the entire changed file, instead of just a delta.
  • If you or someone else clones the repository data afterwards, the previously encrypted files are pulled in an encrypted state. Git-crypt will happely decrypt the files transparently though, if you are in possession of the correct key. The files are therefore seamlessly decrypted when checked out, allowing you to work with them as if they were in plain text. If you then push changes into the repository, the files are encrypted transparently by git-crypt again.
💡
The .gitattributes file is documented in the gitattributes man page. The file pattern format is nearly the same as the one used by .gitignore.

Use Cases

What sets git-crypt apart is its ability to allow you to freely share a repository containing a mix of public and private content.

Even if you're working with developers who don't have the secret key, they can still clone the repository and commit changes on non-encrypted code without any issues. This elegant solution empowers you to store your secret material, such as keys or passwords, in the same repository as your code, without the need to lock down your entire repository.

A simple use case for selfhosters would be, to push your existing Docker Compose configuration files onto a Git management system like GitHub or Gitea, without disclosing sensitive secrets in .env files or others. Usually, one would not push those into a git repository at all but then also lack the possibility for easy backups via git and in general access to your secrets.

Furthermore, as stated on the official GitHub repository:

git-crypt is more secure than other transparent git encryption systems. git-crypt encrypts files using AES-256 in CTR mode with a synthetic IV derived from the SHA-1 HMAC of the file. This mode of operation is provably semantically secure under deterministic chosen-plaintext attack. That means that although the encryption is deterministic (which is required so git can distinguish when a file has and hasn't changed), it leaks no information beyond whether two files are identical or not.

Other proposals for transparent git encryption use ECB or CBC with a fixed IV. These systems are not semantically secure and leak information.

Limitations

It is important to be aware about the limitations of git-crypt:

  • git-crypt relies on git filters, which were not designed with encryption in mind. As such, git-crypt is not the best tool for encrypting most or all of the files in a repository. Where git-crypt really shines is where most of your repository is public, but you have a few files (perhaps private keys named *.key, or a file with API credentials) which you need to encrypt. For encrypting an entire repository, consider using a system like git-remote-gcrypt instead. (Note: no endorsement is made of git-remote-gcrypt's security.)
  • git-crypt does not encrypt file names, commit messages, symlink targets, gitlinks, or other metadata.
  • git-crypt does not hide when a file does or doesn't change, the length of a file, or the fact that two files are identical (see "Security" section above).
  • git-crypt does not support revoking access to an encrypted repository which was previously granted.
  • Files encrypted with git-crypt are not compressible. Even the smallest change to an encrypted file requires git to store the entire changed file, instead of just a delta.
  • Although git-crypt protects individual file contents with a SHA-1 HMAC, git-crypt cannot be used securely unless the entire repository is protected against tampering (an attacker who can mutate your repository can alter your .gitattributes file to disable encryption). If necessary, use git features such as signed tags instead of relying solely on git-crypt for integrity.
  • Files encrypted with git-crypt cannot be patched with git-apply, unless the patch itself is encrypted. To generate an encrypted patch, use git diff --no-textconv --binary. Alternatively, you can apply a plaintext patch outside of git using the patch command.
  • git-crypt does not work reliably with some third-party git GUIs, such as Atlassian SourceTree and GitHub for Mac. Files might be left in an unencrypted state.

Proof-of-Concept

Let's create an example git repository and configure git-crypt for it.

Our simplified repository will hold a docker-compose.yml file as well as a corresponding environment file .env that contains our sensitive secrets like passwords and usernames for the Docker container(s).

Our goal: The Docker Compose file should remain unencrypted and the env file must be encrypted. This way, someone with normal access to the repo can inspect all Docker Compose definitions but not our productively used secrets. On the other side, we with access to the git-crypt symmetric key, can transparently encrypt and decrypt all files on the go, during push, pull, clone etc. without a hassle.

Examle git repo without git-crypt in use
Docker Compose file visible regularly - no git-crypt in use
.env secrets file visible regularly - no git-crypt in use

As the example git repo is configured for now, let's start enabling git-crypt for it:

# install git-crypt; may read https://github.com/AGWA/git-crypt/blob/master/INSTALL.md
sudo apt install git-crypt

# clone your git repo
git clone <YOUR-REPOSITORY>
cd <YOUR-REPOSITORY-NAME>

# initialite git-crypt, which will create the crypto keys
git-crypt init

# export your crypto key to a secure place
# ensure to not include in your git repo itself
git-crypt export-key <PATH-TO-SECURE-KEY-STORAGE>

# define the to be encrypted files (here .env) via a filter
echo -n '**/.env filter=git-crypt diff=git-crypt' > .gitattributes

Now, as soon as you push code changes of your .env file to the git repository, all contents within said .env file will be encrypted by git-crypt automatically. A normal user with access to the repo won't be able to inspect the content of the environment files, as they were securely encrypted by git-crypt.

Native .env is encrypted and cannot be inspected

However, if you are in possession of the symmetric secret key, Git-crypt will transparently decrypt the environment files again and you will see them as if they were regular plaintext files. Note the new MY-NEW-KEY secret introduced to simulate a change.

🛑
Note that previously committed secret files are still unencrypted and likely available in the git commit history. Ensure to start with a fresh branch and secure all your secrets!
git-crypt transparently decrypts .env files

If you pull or clone the repository data onto a new machine, you must unlock the git-crypt encrypted files with your previously exported secret key. You can do so as follows:

# move your exported git-crypt secret key to the new machine

# clone the repo regularly
git clone <YOUR-REPOSITORY>
cd <YOUR-REPOSITORY-NAME>

# unlock the encrypted repo files with your key
git-crypt unlock <PATH-TO-KEY>
unlocking .env files on new machine

You can also lock decrypted files again via the git-crypt lock command:

locking repo files again with git-crypt
💡
Git-crypt also allows you to share your repo code with others easily.

git-crypt add-gpg-user USER_ID

USER_ID can be a key ID, a full fingerprint, an email address, or anything else that uniquely identifies a public key to GPG . Note that git-crypt add-gpg-user will add and commit a GPG-encrypted key file in the .git-crypt directory of the root of your repository.

Cheers!