Safely Keep Your Application Secrets in Repos Even if They’re Public Using Git-Crypt
By: Michael Bogan
Tags: Security, DevOps, Heroku, Git, Programming

git-crypt

Git-crypt aims to solve this problem by encrypting your secrets whenever you push them to your Git repository and decrypting them whenever you pull them. This happens transparently from your point of view. So the secrets are in cleartext as far as you and your deployment code are concerned, but nobody else can read them — even if your source code is in a public GitHub repository.

Let’s look at an example.

1. Install git-crypt

There are instructions for Linux, Mac, and Windows on the git-crypt install page.

If, like me, you’re using a Mac with Homebrew installed, you can run:

$ brew install git-crypt

2. Create a new Git repository

$ mkdir myproject

$ cd myproject

$ git init

$ echo "This is some text" > file.txt

$ git add file.txt

$ git commit -m "Initial commit"

Now we have a Git repository containing a single text file.

3. Set up the repository to use git-crypt

$ git-crypt init

You should see the output:

Generating key...

Before we do anything else, please run the following command:

$ git-crypt export-key ../git-crypt-key

This command creates a copy of the git-crypt symmetric key that was generated for this repository. We’re putting it in the directory above this repository so that we can reuse the same key across multiple Git repositories.

By default, git-crypt stores the generated key in the file .git/git-crypt/keys/default , so you can achieve the same result by running cp .git/git-crypt/keys/default ../git-crypt-key .

This git-crypt-key file is important. It's the key that can unlock all the encrypted files in our repository. We'll see how to use this key later on.

4. Tell git-crypt which files to encrypt

Imagine our application needs an API key, and we want to store it in a file called api.key .

Before we add that file to our repository, we will tell git-crypt that we want the api.key file to be encrypted whenever we commit it.

We do that by using the .gitattributes file. This is a file we can use to add extra metadata to our Git repository. It's not specific to git-crypt, so you might already have a .gitattributes file in your repository. If so, just add the relevant lines. Don't replace the whole file.

In our case, we don’t have a .gitattributes file, so we need to create one. The .gitattributes file contains lines of the form:

[file pattern] attr1=value1 attr2=value2

For git-crypt, the file pattern needs to match all the files we want git-crypt to encrypt, and the attributes are always the same: filter and diff , both of which we set to git-crypt .

So, our .gitattributes file should contain this:

api.key filter=git-crypt diff=git-crypt

Create that file, and add and commit it to your Git repository:

$ echo "api.key filter=git-crypt diff=git-crypt" > .gitattributes

$ git add .gitattributes

$ git commit -m "Tell git-crypt to encrypt api.key"

I’ve used the literal filename api.key in my .gitattributes file, but it can be any file pattern that includes the file(s) you want to encrypt, so I could have used *.key , for instance. Alternatively, you can just add a line for each file you want to encrypt.

It can be easy to make a mistake in your .gitattributes file if you’re trying to protect several files with a single pattern entry. So, I strongly recommend reading this section of the git-crypt README, which highlights some of the common gotchas.

5. Add a secret

Now that we have told git-crypt we want to encrypt the api.key file, let's add that to our repository.

It’s always a good idea to test your setup by adding a dummy value first and confirming that it’s successfully encrypted before committing your real secret.

$ echo "dummy value" > api.key

We haven’t added api.key to Git yet, but we can check what git-crypt is going to do by running:

$ git-crypt status

You should see the following output:

encrypted: api.key

not encrypted: .gitattributes

not encrypted: file.txt

So, even though the api.key file has not yet been committed to our Git repository, this tells you that git-crypt is going to encrypt it for you.

Let’s add and commit the file:

$ git add api.key

$ git commit -m "Added the API key file"

6. Confirm that our secret is encrypted

We’ve told git-crypt to encrypt api.key , and we've added api.key to our repository. However, if we look at api.key , nothing seems different:

$ cat api.key

dummy value

The reason for this is that git-crypt transparently encrypts and decrypts files as you push and pull them to your repository. So, the api.key file looks like a normal cleartext file.

$ file api.key

api.key: ASCII text

One way to confirm that your files really are being encrypted is to push your repository to GitHub. When you view the api.key file using the GitHub web interface, you'll see that it's an encrypted binary file rather than text.

An easier way to see how the repository would look to someone without the decryption key is to run:

$ git-crypt lock

Now if we look at our api.key file, things are different:

$ file api.key

api.key: data $ cat api.key

GITCRYPTROܮ7y\R*^

You will see some different garbage output to what I get, but it’s clear the file is encrypted. This is what would be stored on GitHub.

To go back to having a cleartext api.key file, run:

$ git-crypt unlock ../git-crypt-key