In this post I explain how to use gopass
to GPG encrypt and store your secrets locally, then integrate with direnv
to decrypt and load your secrets to environment variables in your shell without exposing them in plaintext.
In Part 1 of this Secrets Management series I showed you:
- How you can manage secrets securely during software development using environment variables instead of storing them in plaintext.
- How to use
direnv
to automatically load and unload environment variables in your shell from a.envrc
file in a directory. - How to configure your global
.gitignore
to make sure you don’t accidentally commit your.envrc
file.
This post relies on you having read Part 1 and understanding how to do the above. If you haven’t read it, I strongly recommend doing so now. Go on. I’ll wait.
Why bother?
After following the guidance in Part 1, you should now be leveraging direnv
to load your secrets into environment variables in your shell. This approach is much better than hardcoding secrets in application code, and we have put safeguards in place to make sure our .envrc
file is not committed to Git repositories. However, we are still storing secrets in plaintext, which carries a risk.
Anyone with access to this file has access to the secrets. By encrypting and storing secrets in a local secrets manager and retrieving them at runtime, you reduce the risk of your .envrc
file being compromised. A malicious actor would need access to the key to decrypt the secrets manager to access the secrets.
You can further mitigate the risk by using hardware tokens, such as a YubiKey. Setting up GPG to work with YubiKey is outside the scope of this post, however. There are resources available for you to follow if you’re interested, and it is recommended.
About gopass
gopass is a fully offline secrets manager that you can use to store secrets locally on your device via the command line. Secrets are GPG encrypted by default and kept in a dedicated secrets store.
It is an implementation of the popular Unix secret manager pass
, but it has a few additional features that I feel make it superior:
- Support for multiple password stores, allowing you to mount the one you need
- Built-in Git integration
- Support for team sharing
- Support for Yubikeys
I recommend reading the documentation for gopass
, as it has lots more features than we’re going to use here (including offline checks against HIBP).
Prerequisite: Install GPG and create a key pair
Note 1: If you have gpg
installed and have a key pair you want to use already, you can skip to the next step to install gopass
Note 2: I am assuming you have git
installed as well. Install it now if you haven’t.
gopass
uses GPG to encrypt password stores, so you will need gpg
installed and a key pair generated to use.
Install GPG
Ubuntu/Debian:
apt update apt install gnupg rng-tools
macOS:
brew install gnupg2
Generate a key pair
If you’ve already installed GPG and you’re not sure if you have any keys, you can check with the command:
gpg --list-secret-keys

If you get a blank response, you have no keys.
Let’s generate a key pair:
gpg --full-generate-key

Enter the following when asked:
- Key type
- Select either
RSA and RSA
orDSA and Elgamal
- Select either
- Key size
- Recommended at least 2048; the default is fine
- How long its valid for
- Can be long lived; a minimum of 5 years is recommended
- Enter your name and email address
- Enter a password for the key pair
- This should be a strong password
- You will have to enter this password to use the GPG key to unlock the password store, so make a note of it.
Once you’ve done this, you will have a shiny new GPG key pair:

Installing gopass
Ubuntu/Debian
You can install the .deb
package directly:
wget https://github.com/gopasspw/gopass/releases/download/v1.15.14/gopass_1.15.14_linux_amd64.deb sudo dpkg -i gopass_1.15.14_linux_amd64.deb
Or via APT:
curl https://packages.gopass.pw/repos/gopass/gopass-archive-keyring.gpg | sudo tee /usr/share/keyrings/gopass-archive-keyring.gpg cat << EOF | sudo tee /etc/apt/sources.list.d/gopass.sources Types: deb URIs: https://packages.gopass.pw/repos/gopass Suites: stable Architectures: amd64 arm64 armhf Components: main Signed-By: /usr/share/keyrings/gopass-archive-keyring.gpg EOF sudo apt install gopass-archive-keyring gopass
Note:
Debian based OSs ship with a package called gopass
that is not related to this project. If installing via apt
, make sure to add this source first.
Alpine Linux:
apk add gopass
macOS/Homebrew:
brew install gopass
Using gopass
Once installed, you can create your password store. This is when you will need a GPG key pair (either a pre-existing pair, or the pair you’ve just generated). If you have more than one GPG key pair, you will be asked which one you want to use. If you’ve only got one, gopass
will use that.
Create your store using:
gopass init

Your password store is now set up. You can view it with:
gopass list

Adding a secret
Gopass
stores secrets in a tree-like structure, allowing you to organise them at different levels. New secrets can be added by using insert
with the path and name you want to store the secret under, for example:
gopass insert slack/prod/bot-token
You will then be asked to enter the secret you want to store.

You can add multiple secrets and create multiple levels. View the secrets you have stored with gopass list
:

Accessing a secret
To access a secret you have stored, use the show
command:
gopass show slack/prod/bot-token
You will then be asked to enter the password for your GPG key pair to decrypt the secret.

You’ve now installed, configured and stored a secret in gopass
, and we’re ready to integrate this into our development workflow.
Using gopass secrets in development
I’m going to reuse the sample project from Part 1, which has these files:

.envrc
contains our environment variables, which have been loaded into our shell by direnv
when we navigated to the directory:

example.py
is our simple script which prints out these variables:

I’m going to remove the plaintext password in .envrc
, and instead retrieve it from our local gopass
store.
First, I’m going to add the new secret WinterIsComing
for jon.snow
into our secrets store:
gopass insert user_accounts/jon.snow
Let’s view it to check it is correct:

Now I’m going to modify our .envrc
file to retrieve the secret at runtime. We do this by using bash command substitution and the same command we use to view the secret via terminal. We replace the plaintext password in our file with:
export PASSWORD=$(gopass show path/to/secret)

I need to approve my .envrc
file in direnv
, as it has been modified. Lets see what happens when I do:

I’m asked for the password for the GPG key pair I’m using with gopass
. This will decrypt the secret and allow it to be stored in the environment variable.
Now let’s run example.py
and confirm the secret has been retrieved and stored correctly:

Success! We’ve now retrieved and decrypted our secret for use in our application, and we’ve not stored it in plaintext anywhere.
Conclusion
You’ve now built on the good practices you learned in Part 1 to make sure secrets are never stored in plaintext by:
- Encrypting secrets and storing them in a local secrets store using
gopass
. - Modifying your
.envrc
file to retrieve and decrypt secrets, instead of storing them in plaintext. - Not having to make any changes to your application code, as you are already using secrets loaded from environment variables.
Leave a Reply