How can I save my secret keys and password securely in my version control system?

Posted on

Question :

How can I save my secret keys and password securely in my version control system?

I keep important settings like the hostnames and ports of development and production servers in my version control system. But I know that it’s bad practice to keep secrets (like private keys and database passwords) in a VCS repository.

But passwords–like any other setting–seem like they should be versioned. So what is the proper way to keep passwords version controlled?

I imagine it would involve keeping the secrets in their own “secrets settings” file and having that file encrypted and version controlled. But what technologies? And how to do this properly? Is there a better way entirely to go about it?

I ask the question generally, but in my specific instance I would like to store secret keys and passwords for a Django/Python site using git and github.

Also, an ideal solution would do something magical when I push/pull with git–e.g., if the encrypted passwords file changes a script is run which asks for a password and decrypts it into place.

EDIT: For clarity, I am asking about where to store production secrets.

Asked By: Chris W.


Answer #1:

You’re exactly right to want to encrypt your sensitive settings file while still maintaining the file in version control. As you mention, the best solution would be one in which Git will transparently encrypt certain sensitive files when you push them so that locally (i.e. on any machine which has your certificate) you can use the settings file, but Git or Dropbox or whoever is storing your files under VC does not have the ability to read the information in plaintext.

Tutorial on Transparent Encryption/Decryption during Push/Pull

This gist shows a tutorial on how to use the Git’s smudge/clean filter driver with openssl to transparently encrypt pushed files. You just need to do some initial setup.

Summary of How it Works

You’ll basically be creating a .gitencrypt folder containing 3 bash scripts,


which are used by Git for decryption, encryption, and supporting Git diff. A master passphrase and salt (fixed!) is defined inside these scripts and you MUST ensure that .gitencrypt is never actually pushed.
Example clean_filter_openssl script:


SALT_FIXED=<your-salt> # 24 or less hex characters

openssl enc -base64 -aes-256-ecb -S $SALT_FIXED -k $PASS_FIXED

Similar for smudge_filter_open_ssl and diff_filter_oepnssl. See Gist.

Your repo with sensitive information should have a .gitattribute file (unencrypted and included in repo) which references the .gitencrypt directory (which contains everything Git needs to encrypt/decrypt the project transparently) and which is present on your local machine.

.gitattribute contents:

* filter=openssl diff=openssl
    renormalize = true

Finally, you will also need to add the following content to your .git/config file

[filter "openssl"]
    smudge = ~/.gitencrypt/smudge_filter_openssl
    clean = ~/.gitencrypt/clean_filter_openssl
[diff "openssl"]
    textconv = ~/.gitencrypt/diff_filter_openssl

Now, when you push the repository containing your sensitive information to a remote repository, the files will be transparently encrypted. When you pull from a local machine which has the .gitencrypt directory (containing your passphrase), the files will be transparently decrypted.


I should note that this tutorial does not describe a way to only encrypt your sensitive settings file. This will transparently encrypt the entire repository that is pushed to the remote VC host and decrypt the entire repository so it is entirely decrypted locally. To achieve the behavior you want, you could place sensitive files for one or many projects in one sensitive_settings_repo. You could investigate how this transparent encryption technique works with Git submodules if you really need the sensitive files to be in the same repository.

The use of a fixed passphrase could theoretically lead to brute-force vulnerabilities if attackers had access to many encrypted repos/files. IMO, the probability of this is very low. As a note at the bottom of this tutorial mentions, not using a fixed passphrase will result in local versions of a repo on different machines always showing that changes have occurred with ‘git status’.

Answered By: dgh

Answer #2:

Heroku pushes the use of environment variables for settings and secret keys:

The traditional approach for handling such config vars is to put them under source – in a properties file of some sort. This is an error-prone process, and is especially complicated for open source apps which often have to maintain separate (and private) branches with app-specific configurations.

A better solution is to use environment variables, and keep the keys out of the code. On a traditional host or working locally you can set environment vars in your bashrc. On Heroku, you use config vars.

With Foreman and .env files Heroku provide an enviable toolchain to export, import and synchronise environment variables.

Personally, I believe it’s wrong to save secret keys alongside code. It’s fundamentally inconsistent with source control, because the keys are for services extrinsic to the the code. The one boon would be that a developer can clone HEAD and run the application without any setup. However, suppose a developer checks out a historic revision of the code. Their copy will include last year’s database password, so the application will fail against today’s database.

With the Heroku method above, a developer can checkout last year’s app, configure it with today’s keys, and run it successfully against today’s database.

Answered By: Colonel Panic

Answer #3:

The cleanest way in my opinion is to use environment variables. You won’t have to deal with .dist files for example, and the project state on the production environment would be the same as your local machine’s.

I recommend reading The Twelve-Factor App‘s config chapter, the others too if you’re interested.

Answered By: Samy Dindane

Answer #4:

An option would be to put project-bound credentials into an encrypted container (TrueCrypt or Keepass) and push it.

Update as answer from my comment below:

Interesting question btw. I just found this: which looks very promising for automatic encryption

Answered By: schneck

Answer #5:

I suggest using configuration files for that and to not version them.

You can however version examples of the files.

I don’t see any problem of sharing development settings. By definition it should contain no valuable data.

Answered By: tiktak

Answer #6:

Since asking this question I have settled on a solution, which I use when developing small application with a small team of people.


git-crypt uses GPG to transparently encrypt files when their names match certain patterns. For intance, if you add to your .gitattributes file…

*.secret.* filter=git-crypt diff=git-crypt

…then a file like config.secret.json will always be pushed to remote repos with encryption, but remain unencrypted on your local file system.

If I want to add a new GPG key (a person) to your repo which can decrypt the protected files then run git-crypt add-gpg-user <gpg_user_key>. This creates a new commit. The new user will be able to decrypt subsequent commits.

Answered By: Chris W.

Answer #7:

BlackBox was recently released by StackExchange and while I have yet to use it, it seems to exactly address the problems and support the features requested in this question.

From the description on

Safely store secrets in a VCS repo (i.e. Git or Mercurial). These
commands make it easy for you to GPG encrypt specific files in a repo
so they are “encrypted at rest” in your repository. However, the
scripts make it easy to decrypt them when you need to view or edit
them, and decrypt them for for use in production.

Answered By: Chris W.

Answer #8:

I ask the question generally, but in my specific instance I would like
to store secret keys and passwords for a Django/Python site using git
and github.

No, just don’t, even if it’s your private repo and you never intend to share it, don’t.

You should create a put it on VCS ignore and in your do something like

from local_settings import DATABASES, SECRET_KEY


If your secrets settings are that versatile, I am eager to say you’re doing something wrong

Leave a Reply

Your email address will not be published. Required fields are marked *