Skip to main content

johnslock

Encrypted secrets that replace your .env file.

.env files are plaintext. You can't commit them, you end up mailing them around, and one stray cat in a screen share leaks everything. johnslock keeps the same one-file-per-project workflow, but the file is encrypted and the key that opens it never lives in the project directory.

pip install johnslock

johnslock init
johnslock lock API_KEY=verysecretkey
import johnslock

api_key = johnslock.require("API_KEY")

That's it. Commit .johnslock — it is ciphertext.

How it works

File Where Contains
.johnslock your project, committed AES-256-GCM ciphertext of every secret
master.key ~/.johnslock/, mode 0600, never committed the key that opens your vaults

The vault stores one ciphertext covering all secrets at once, so the file leaks neither values nor names. Each vault has a random id and salt, both authenticated, so ciphertext cannot be moved between vaults or edited without detection. The data key is derived per vault with HKDF-SHA256 from the master key.

Steal the .johnslock file on its own and you get nothing usable — no names, no values, and no way to test a guess offline, because there is no password to guess.

Commands

johnslock init                       # create the vault + this machine's master key
johnslock init --passphrase          # ...and protect the master key with a passphrase
johnslock lock API_KEY=verysecretkey # encrypt a secret in
johnslock lock DB_PASSWORD           # type the value at a hidden prompt instead
johnslock list                       # names only, never values
johnslock unlock API_KEY             # print one value (for shells and scripts)
johnslock rm API_KEY                 # remove a secret
johnslock run -- python app.py       # run something with the secrets in its env
johnslock path                       # where is my vault, where is my key
johnslock export                     # make a shareable bundle + a one-time key
johnslock import bundle --key KEY    # merge someone's bundle into your vault

johnslock unlock NAME prints the bare value, so shell use is easy:

export API_KEY="$(johnslock unlock API_KEY)"

Sending secrets to someone else

Your master key never leaves your machine, so you can't just send the vault. export re-encrypts the secrets under a fresh one-time key instead:

$ johnslock export
bundle       /work/myapp/myapp.johnslock-export  (3 secrets)
transfer key K7QMT-3XBVR-9HJZD-2PWLS-6NCFA

Send the bundle and the key over two different channels.
They unlock it with:
  johnslock import myapp.johnslock-export --key K7QMT-3XBVR-9HJZD-2PWLS-6NCFA
The key is stored nowhere — copy it now.

The transfer key carries ~116 bits of entropy, is stretched with scrypt, and is stored nowhere — not in the bundle, not on disk. Send the bundle over one channel (email, Slack, a file drop) and the key over another (a phone call, a Signal message). On the other side:

johnslock import myapp.johnslock-export --key K7QMT-3XBVR-9HJZD-2PWLS-6NCFA

The secrets land in their vault, re-encrypted under their master key. The transfer key is dead weight afterwards.

Python API

import johnslock

johnslock.require("API_KEY")        # raises SecretNotFoundError if missing
johnslock.get("DEBUG", "0")         # returns a default instead
johnslock.names()                   # ['API_KEY', 'DEBUG'] — no values
johnslock.as_dict()                 # every secret, as a plain dict
johnslock.load()                    # copy everything into os.environ
johnslock.load(override=True)       # ...even over existing env vars

load() leaves existing environment variables alone by default, so a real deployment's configuration is never silently replaced by a checked-in vault.

The vault is found by walking up from the current directory, exactly like .env. Secrets are cached in memory and re-read automatically when the file changes.

Drop-in replacement for python-dotenv:

# before
from dotenv import load_dotenv; load_dotenv()

# after
import johnslock; johnslock.load()

The strongest setting

johnslock init --passphrase encrypts the master key itself with a passphrase you type (scrypt + AES-GCM). Then nothing on disk opens your vault — an attacker with a full copy of your laptop still needs something that exists only in your head.

For CI, set the passphrase in the environment:

export JOHNSLOCK_PASSPHRASE="..."
johnslock run -- pytest

Threat model — read this

johnslock is honest about what encryption on your own machine can and cannot do.

It protects you against:

  • committing secrets in plaintext, and leaking them through git history
  • a stolen, copied, or accidentally published .johnslock file
  • someone reading your screen, your repo, or a backup of it
  • tampering — any edit to the vault is detected, not silently decrypted
  • sending secrets to a colleague without a plaintext file existing anywhere

It does not protect you against:

  • someone who already runs code as you on your machine in default mode. They can read ~/.johnslock/master.key, exactly as they could read .env. Use --passphrase mode to close this gap.
  • malware that reads your process memory after you have decrypted secrets
  • a secret you print to a log, paste into a chat, or johnslock unlock into a shell history

No local tool can make a secret readable by your program but unreadable by you. What johnslock does is stop the secret ever sitting on disk in the clear, and keep the key out of the directory the file lives in.

Environment variables

Variable Effect
JOHNSLOCK_HOME keystore directory (default ~/.johnslock)
JOHNSLOCK_VAULT use this vault file instead of searching upward
JOHNSLOCK_PASSPHRASE passphrase for passphrase-mode keys, for CI

Crypto

  • AES-256-GCM for every ciphertext (authenticated, tamper-evident)
  • HKDF-SHA256 for per-vault key derivation from the master key
  • scrypt (N=2^15, r=8, p=1) for passphrase-derived keys
  • all randomness from os.urandom
  • one dependency: cryptography

Development

pip install -e ".[dev]"
pytest --cov=johnslock
./publish.sh --build-only

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

johnslock-0.1.0.tar.gz (24.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

johnslock-0.1.0-py3-none-any.whl (21.7 kB view details)

Uploaded Python 3

File details

Details for the file johnslock-0.1.0.tar.gz.

File metadata

  • Download URL: johnslock-0.1.0.tar.gz
  • Upload date:
  • Size: 24.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for johnslock-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0b6a11e6c53206bcbe7fae82c19ef0b8ef5e8c297ee7bc8bce1608beaec28186
MD5 88a699d482fac8d643e1d9e4a2daf7ec
BLAKE2b-256 eec3fb8d25503da97c0d3354f3b72df85a2c81866aecc805381437a68b099ef3

See more details on using hashes here.

File details

Details for the file johnslock-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: johnslock-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for johnslock-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6de16903d357d53791d4b5bc31e154ac45c2411259d254e1ffa22f1a5d8daa13
MD5 0b692499fa70f9fa43b8c1c98f793883
BLAKE2b-256 90491dfc30c6f3e39394df1498a71194c440bb6f0e6174882fa509df888773b4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page