Encrypted .env credential manager. Inspired by Rails credentials.
Encrypt your secrets with AES-256-GCM, commit .env.enc files to version control, and decrypt them at runtime. Works as a CLI tool and as a Go package.
go install github.com/aschiavon91/krypt/cmd/krypt@latest# Generate an encryption key and save to a file
krypt keygen > .krypt-key
chmod 600 .krypt-key
# Create your secrets file
cat > .env <<EOF
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=sk_live_abc123
REDIS_URL=redis://localhost:6379
EOF
# Encrypt it
krypt encrypt --key-file .krypt-key
# -> creates .env.enc (add this to git)
# -> add .env and .krypt-key to .gitignore
# Decrypt to stdout
krypt decrypt --key-file .krypt-key
# Run a command with secrets injected
krypt run --key-file .krypt-key -- go run ./cmd/server
# Edit secrets in your $EDITOR
krypt edit --key-file .krypt-key
# Set/get individual keys
krypt set "STRIPE_KEY=sk_live_new" --key-file .krypt-key
krypt get STRIPE_KEY --key-file .krypt-keyFor convenience in a shell session, export the key as an env var instead of passing --key-file every time:
export KRYPT_KEY=$(cat .krypt-key)
krypt encrypt
krypt decrypt
krypt run -- go run ./cmd/serverkrypt supports named environments with separate keys:
# File naming convention:
# .env.enc -> KRYPT_KEY
# .env.dev.enc -> KRYPT_KEY_DEV
# .env.staging.enc -> KRYPT_KEY_STAGING
# Generate a key for dev
krypt keygen > .krypt-key-dev
# Encrypt dev secrets
krypt encrypt dev --key-file .krypt-key-dev
# Decrypt dev secrets
krypt decrypt dev --key-file .krypt-key-dev
# Run with dev secrets
krypt run dev --key-file .krypt-key-dev -- go run ./cmd/server
# Edit dev secrets
krypt edit dev --key-file .krypt-key-dev
# Set/get in dev
krypt set "DEBUG=true" dev --key-file .krypt-key-dev
krypt get DEBUG dev --key-file .krypt-key-devOr with env vars:
export KRYPT_KEY_DEV=$(cat .krypt-key-dev)
krypt run dev -- go run ./cmd/serverGenerate a new 32-byte encryption key (64 hex characters).
krypt keygen
# a1b2c3d4e5f6...
# Save to a file (recommended)
krypt keygen > .krypt-keyEncrypt a plaintext .env file into .env.enc. The write is atomic (temp file + rename) to prevent data loss.
krypt encrypt # .env -> .env.enc
krypt encrypt staging # .env.staging -> .env.staging.encDecrypt and print to stdout. Useful for piping.
krypt decrypt
krypt decrypt dev | grep DATABASEOpen decrypted secrets in $VISUAL or $EDITOR (falls back to vi). Re-encrypts on save. If the editor exits non-zero, changes are discarded.
The temp file is created in /dev/shm (Linux) when available for in-memory storage, otherwise /tmp. The file is created with a restrictive umask (0177) so it is never readable by other users, and cleaned up immediately after the editor exits.
krypt edit
krypt edit stagingDecrypt secrets and inject them into the environment of a subprocess. Secrets override existing env vars with the same name. The exit code of the child process is passed through.
For security, KRYPT_KEY and KRYPT_KEY_* env vars are stripped from the child process environment -- the subprocess only sees the decrypted secrets, never the master encryption key.
krypt run -- go run ./cmd/server
krypt run dev -- npm start
krypt run staging -- docker compose upSet or update a single key in an encrypted file.
krypt set "DATABASE_URL=postgres://prod-host/myapp"
krypt set "DEBUG=true" devGet a single value from an encrypted file. Exits non-zero if the key is not found.
krypt get DATABASE_URL
krypt get DEBUG dev| Flag | Short | Description |
|---|---|---|
--key-file |
Read encryption key from a file (recommended) | |
--key |
-k |
Provide encryption key directly (caution: visible in ps and shell history) |
--file |
-f |
Override encrypted file path |
--source |
Override source .env file path (encrypt only) |
--keyflag (direct value)--key-fileflag (read from file)KRYPT_KEY_<ENV>environment variable (if env specified)KRYPT_KEYenvironment variable- Error with helpful message
The pkg/krypt package has zero external dependencies (pure stdlib) and can be imported into any Go project.
go get github.com/aschiavon91/kryptimport "github.com/aschiavon91/krypt/pkg/krypt"key, _ := hex.DecodeString(os.Getenv("KRYPT_KEY"))
secrets, err := krypt.Load(".env.enc", key)
if err != nil {
log.Fatal(err)
}
db.Connect(secrets["DATABASE_URL"])key, _ := hex.DecodeString(os.Getenv("KRYPT_KEY"))
if err := krypt.Autoload(".env.enc", key); err != nil {
log.Fatal(err)
}
// os.Getenv("DATABASE_URL") now workskeyHex, _ := krypt.GenerateKey()
key, _ := hex.DecodeString(keyHex)
content := []byte("DATABASE_URL=postgres://localhost/myapp\nAPI_KEY=secret\n")
if err := krypt.Encrypt(content, ".env.enc", key); err != nil {
log.Fatal(err)
}key, _ := hex.DecodeString(os.Getenv("KRYPT_KEY"))
// Set
krypt.Set(".env.enc", key, "NEW_SECRET", "value123")
// Get
val, err := krypt.Get(".env.enc", key, "DATABASE_URL")
if errors.Is(err, krypt.ErrKeyNotFound) {
// key doesn't exist
}func GenerateKey() (string, error)
func Encrypt(plaintext []byte, path string, key []byte) error
func Decrypt(path string, key []byte) ([]byte, error)
func Load(path string, key []byte) (map[string]string, error)
func Autoload(path string, key []byte) error
func Set(path string, key []byte, envKey, envValue string) error
func Get(path string, key []byte, envKey string) (string, error)
var ErrKeyNotFound = errors.New("key not found")All functions accept []byte keys (already decoded from hex). The caller handles hex decoding. No global state, no init(), no singletons.
Encrypted files are raw AES-256-GCM sealed bytes: nonce (12 bytes) || ciphertext || GCM auth tag (16 bytes). A fresh random nonce is generated on every encrypt, so re-encrypting the same content produces different output.
Decrypted content is standard .env format:
# Database config
DATABASE_URL=postgres://localhost:5432/myapp
DATABASE_POOL=10
# API keys
API_KEY=sk_live_abc123Parser rules:
KEY=VALUElines are parsed as env vars#lines and blank lines are preserved on round-trip- Values can be quoted with
"or' - No variable interpolation
- No trailing comments
- AES-256-GCM encryption via Go stdlib (
crypto/aes+crypto/cipher) - 32-byte keys generated from
crypto/rand - Fresh random nonce on every encrypt operation
- Atomic writes -- encrypted files are written to a temp file and renamed, preventing corruption on interrupted writes
- Restrictive file permissions -- encrypted files are written
0600, temp files created with umask0177 - In-memory temp files --
krypt edituses/dev/shmon Linux to avoid writing plaintext to disk - Temp file cleanup -- deferred removal runs even on panic
- Key isolation --
krypt runstripsKRYPT_KEY*env vars from the child process so subprocesses never see the master key --key-fileflag -- preferred over--keyto avoid exposing the key in process listings and shell history
MIT