How to encrypt .env files and developer secrets locally
- Step 1Open the tool on Pro with Mode on Encrypt — The AES-256 Encryptor requires the Pro plan; on Free a Pro overlay blocks it. The Mode dropdown defaults to Encrypt — leave it there to seal a secrets file.
- Step 2Drop your .env or secrets file — Upload one file — input type is
any, so a plain.env, a JSON credentials file, a.pemkey, or a zip of several config files all work. The tool processes one file at a time. On Pro the ceiling is 100 MB, far above any config file. - Step 3Use a shared team passphrase — Type a passphrase of at least 8 characters into the masked field; shorter is rejected with
Passphrase must be at least 8 characters.Use the value your team already keeps in a password/secret manager so every developer who should decrypt can. There is no key file. - Step 4Encrypt locally — PBKDF2 derives the 256-bit key from a fresh 16-byte salt; AES-GCM encrypts under a fresh 12-byte IV — all in your browser, nothing uploaded. The plaintext secrets never leave your machine.
- Step 5Commit the .aes, never the plaintext — You get
<name>.aes(e.g.secrets.env.aes, MIMEapplication/octet-stream) containing salt + IV + ciphertext + tag. Commit that blob; keep the plaintext.envin.gitignore. The repo now versions only ciphertext. - Step 6Teammates decrypt after pulling — A teammate pulls the repo, opens the tool, sets Mode to Decrypt (the picker then hints
.aes), dropssecrets.env.aes, and enters the shared passphrase. The tool strips.aes, returningsecrets.env; a wrong passphrase throwsDecryption failed — wrong passphrase or corrupted file.
Where secrets leak, and how an encrypted blob stops it
Storing only the .aes keeps plaintext secrets out of every system that retains history.
| Leak vector | Plain secret | AES-256-GCM `.aes` blob |
|---|---|---|
| Accidental git commit | Key permanently in history | Only ciphertext in history |
| Public repo / fork | Key harvested by scanners | Useless blob without passphrase |
| Pasted into Slack/email | Key lingers in chat logs | Pass the passphrase via secret manager instead |
| CI logs / build output | Key may be echoed | Decrypt only where the passphrase is injected |
| Repo backup leaked | Full credentials exposed | Ciphertext only |
The two controls — nothing else exists
No cipher menu, no key-file upload, no per-key field, no batch. Only Mode and passphrase; every crypto parameter is fixed in code.
| Control | Type | Default / rule | Team use |
|---|---|---|---|
| Mode | Dropdown: Encrypt / Decrypt | Defaults to Encrypt | Author encrypts; teammates switch to Decrypt |
| Passphrase | Masked password field | Minimum 8 characters | Shared team value from your secret manager |
What the encryptor produces (fixed in code)
Self-contained output you can commit and version like any binary blob.
| Aspect | Value | Note |
|---|---|---|
| Cipher | AES-GCM, 256-bit | Authenticated — tampered blob fails to decrypt |
| Key derivation | PBKDF2, 100,000 iters, SHA-256 | Slows cracking of a leaked blob |
| Output name (encrypt) | <name>.aes | secrets.env -> secrets.env.aes |
| Output name (decrypt) | strips trailing .aes | secrets.env.aes -> secrets.env |
| Blob layout | salt(16) + iv(12) + ciphertext+tag | Only the passphrase is needed to decrypt |
Cookbook
Recipes for keeping secrets out of git and chat, with the exact filenames, byte layout, and error strings the tool produces. Everything runs in your browser tab — no upload, no server, no API.
Encrypt .env and commit only the blob
Keep the plaintext .env gitignored; commit secrets.env.aes. The repo versions ciphertext that scanners and forks cannot read.
Mode: Encrypt Input file: secrets.env (DB_URL, STRIPE_KEY, JWT_SECRET) Passphrase: team-cobalt-meridian-shared Local: salt = 16 random bytes iv = 12 random bytes key = PBKDF2(passphrase, salt, 100000, SHA-256) ct = AES-GCM(key, iv, .env bytes) (tag appended) Downloaded: secrets.env.aes bytes = salt(16) + iv(12) + ciphertext+tag .gitignore: secrets.env git add secrets.env.aes && git commit
Teammate decrypts after cloning
A new developer pulls the repo, decrypts the blob with the shared passphrase from the team secret manager, and gets a working .env.
Mode: Decrypt Input file: secrets.env.aes (from the cloned repo) Passphrase: team-cobalt-meridian-shared Downloaded: secrets.env (byte-for-byte original) -> place it in the project root (gitignored) Wrong shared passphrase: Error: "Decryption failed — wrong passphrase or corrupted file."
Encrypt several config files as one bundle
This tool encrypts one file per run. To seal a set of configs, zip them and encrypt the single archive; teammates decrypt then unzip.
Step 1 zip -> env-bundle.zip (.env, db.json, service-account.json)
Step 2 Mode = Encrypt, drop env-bundle.zip
-> env-bundle.zip.aes
Step 3 commit env-bundle.zip.aes (gitignore the plaintext set)
Step 4 teammate: Decrypt -> env-bundle.zip -> unzip
One shared passphrase unlocks the whole config set.Rotate the passphrase after someone leaves
AES-GCM does not support changing the passphrase in place — you decrypt with the old one and re-encrypt with the new. Each re-encryption gets a fresh salt and IV.
Step 1 Mode = Decrypt, old passphrase
secrets.env.aes -> secrets.env
Step 2 Mode = Encrypt, NEW passphrase
secrets.env -> secrets.env.aes (fresh salt + IV)
Step 3 commit the new secrets.env.aes
Step 4 rotate the underlying API keys too, since the
departed teammate may have copied the plaintext
Note: rotating the passphrase protects the BLOB; rotate the
actual keys to fully revoke access.Pick a team passphrase strong enough for a public leak
Assume the committed blob could end up in a public repo. The 8-character minimum is a floor; choose something that survives offline cracking.
Rejected: "devkey1" -> 7 chars -> "Passphrase must be at least 8 characters." Weak but accepted (don't): "password" -> top of every cracking wordlist Strong, accepted, stored in the team secret manager: "cobalt-meridian-falcon-ledger-2031" Tip: audit it in Password Entropy Auditor — a leaked .aes plus a weak passphrase is crackable offline despite PBKDF2.
Edge cases and what actually happens
Plaintext .env committed alongside the blob
Defeats the protectionEncrypting secrets.env does not stop git from tracking the plaintext if it is not gitignored. If the plaintext .env is committed too, the secrets are exposed in history regardless of the .aes. Add the plaintext to .gitignore and commit only the blob — the tool produces the .aes but cannot manage your repo.
Wrong shared passphrase on decrypt
Decryption failedGCM verifies the auth tag, so a passphrase that does not match the one used to encrypt produces an invalid tag and the tool throws Decryption failed — wrong passphrase or corrupted file. A teammate who has the wrong value just re-fetches the correct passphrase from the secret manager — nothing is consumed or corrupted.
Passphrase shorter than 8 characters
RejectedLength is checked before any crypto runs. Under 8 characters throws Passphrase must be at least 8 characters. and nothing is encrypted. It is a hard floor in code; a stronger passphrase is strongly advised for secrets anyway.
Blob corrupted by a git merge or line-ending conversion
Decryption failedThe .aes is binary; if git or an editor applies CRLF/LF conversion or a botched merge to it, bytes change and the GCM tag fails — decryption throws Decryption failed. Commit .aes files as binary (e.g. via .gitattributes *.aes binary) so they are never line-ending-mangled.
Expecting the tool to inject secrets into CI automatically
Out of scopeThis is a browser tool with no server API and no CI integration. It produces and reverses .aes blobs; it does not push values into a pipeline. For CI, decrypt locally and load the result into your CI's own secret store, or script decryption with a tool that has a CLI — this one is interactive and browser-only.
Decrypting a blob from a different encryption tool
Decryption failedDecrypt assumes the strict layout salt(16) + iv(12) + ciphertext+tag. A .aes or encrypted secret from SOPS, OpenSSL, age, or git-crypt uses a different format, so the first 28 bytes are not a valid salt+IV here and the tag will not verify — you get Decryption failed. This tool only round-trips its own blobs.
Rotating the passphrase but not the API keys
Incomplete revocationRe-encrypting the blob under a new passphrase protects the blob, but anyone who previously decrypted it may have copied the plaintext secrets. To truly revoke a departed teammate, rotate the underlying API keys and credentials as well, then re-encrypt the new values. The encryptor secures storage, not the lifetime of secrets already seen.
Teammate on the Free plan can't decrypt
Pro requiredDecrypting requires Pro — the tool has a minimum tier of Pro in both directions. On Free a Pro overlay reads AES-256 Offline Encryptor requires the Pro plan. Ensure developers who must decrypt have Pro, or distribute the decrypted secrets through another channel they can use.
Re-encrypting an already encrypted blob
SupportedEncrypting a .aes again yields <name>.aes.aes wrapped in a second AES-GCM layer; you would decrypt twice in reverse to recover the original. This is rarely intended — usually it means you grabbed the blob instead of the plaintext when re-encrypting after a rotation.
Renaming the blob loses the original filename
ExpectedDecrypt only strips a trailing .aes. If you renamed secrets.env.aes to s.bin, decrypt returns the correct bytes but cannot restore the original name — it just removes a .aes suffix if present. Keep the .aes suffix so it round-trips back to secrets.env.
Frequently asked questions
Is it safe to commit the encrypted .env to a public repo?
The committed .aes is ciphertext, so a public repo exposes only an opaque blob — but only if the passphrase is strong. Assume scanners will grab it and attempt offline cracking; PBKDF2's 100,000 iterations slow that, but a weak passphrase still falls. Use a long, high-entropy shared passphrase and never commit the plaintext alongside it.
How do teammates get the passphrase without leaking it?
Distribute it through your team's secret manager (1Password, Vault, etc.) rather than Slack or email. The repo carries the encrypted blob; the manager carries the one passphrase that opens it. New developers fetch the passphrase from the manager and decrypt the blob locally to get a working .env.
Does this integrate with CI/CD or a secret store?
No — it is a browser tool with no server API and no pipeline hooks. It produces and reverses .aes blobs interactively. For CI, decrypt locally and load the values into your CI's native secret store, or use a CLI-based tool (SOPS, age) for automated pipelines. This tool is for human, browser-based encrypt/decrypt.
What happens if git mangles the .aes file?
The .aes is binary; a CRLF/LF conversion or a bad merge changes bytes, invalidating the GCM auth tag so decryption throws Decryption failed — wrong passphrase or corrupted file. Mark .aes as binary in .gitattributes (*.aes binary) so git never applies text transformations to it.
Can JAD see the secrets I encrypt?
No. Encryption runs entirely in your browser via the Web Crypto API, and the passphrase is derived to a key locally and never transmitted. JAD never receives your .env, your keys, your passphrase, or your derived key — there is no server-side copy of your secrets to leak.
How do I rotate the passphrase when someone leaves?
Decrypt the blob with the old passphrase, then re-encrypt the plaintext with a new one (each run generates a fresh salt and IV) and commit the new .aes. Crucially, also rotate the underlying API keys, because anyone who decrypted before may have copied the plaintext — re-encryption protects storage, not secrets already seen.
Can I encrypt several config files together?
Not in one run — the tool encrypts one file at a time. Zip the configs into a single archive and encrypt that; teammates decrypt then unzip. One shared passphrase unlocks the whole bundle, and you commit a single .aes.
Will a tampered blob give me a subtly wrong .env?
No. AES-GCM is authenticated, so any change to the blob invalidates the tag and decryption fails outright with Decryption failed. You never get a silently altered .env that breaks your app in confusing ways — it either returns the exact original config or it errors clearly.
How strong should the shared passphrase be?
Treat the committed blob as potentially public, so use a long passphrase of several random words. The tool enforces only an 8-character minimum, which is a floor. Audit candidates with Password Entropy Auditor — a leaked .aes plus a weak passphrase is crackable offline despite the PBKDF2 iterations.
How can I verify the decrypted .env matches what I committed?
Fingerprint the plaintext before encrypting and the decrypted output after, then compare with Multi-Hash Fingerprinter; matching SHA-256 digests prove a lossless round-trip. GCM would already have rejected a tampered blob, so a hash match confirms both integrity and that the right file came back.
Is this the right tool for signing a release instead of encrypting secrets?
No — this is symmetric encryption for keeping a file confidential. To sign a message or artifact so others can verify it came from you (or to encrypt to a teammate's public key without sharing a passphrase), use PGP Message Signer. Use the AES-256 Encryptor when the goal is sealing a secrets file behind a shared passphrase.
Can I check whether a committed blob is genuinely encrypted?
Yes — properly encrypted data has near-maximal entropy. Run the .aes through Entropy Analyzer; high, flat entropy confirms it looks like ciphertext rather than accidentally committed plaintext. This is a quick sanity check before you trust that a blob is safe to push.
Privacy first
Every JAD Security operation runs entirely in your browser. Files, passwords, and PGP private keys never leave your device — verified by zero outbound network requests during processing.