How It Works
The encryption flow
Section titled “The encryption flow”envsh uses a hybrid encryption scheme: AES-256-GCM for bulk data, with per-recipient key wrapping using SSH keys.
Push (encrypt)
Section titled “Push (encrypt)”.env file (plaintext) │ ├─ 1. Generate random AES-256 key ├─ 2. Derive wrapping key via HKDF-SHA256 │ Salt: "envsh-v1" Info: "aes-key-wrap" ├─ 3. Encrypt plaintext with AES-256-GCM (random 12-byte nonce) ├─ 4. For each recipient (team member + machines): │ ├─ Convert their Ed25519 public key → X25519 │ ├─ Generate ephemeral X25519 keypair │ ├─ ECDH → shared secret │ ├─ HKDF → wrapping key │ └─ AES-GCM encrypt the AES key with wrapping key ├─ 5. Zero the AES key from memory └─ 6. Upload: ciphertext + nonce + auth_tag + wrapped keysPull (decrypt)
Section titled “Pull (decrypt)”Download bundle from server │ ├─ 1. Find our entry by SSH key fingerprint ├─ 2. Convert our Ed25519 private key → X25519 ├─ 3. ECDH with ephemeral public key → shared secret ├─ 4. HKDF → wrapping key ├─ 5. Unwrap AES key ├─ 6. Decrypt ciphertext with AES-256-GCM ├─ 7. Verify SHA-256 checksum └─ 8. Zero the AES key from memoryWhat the server stores
Section titled “What the server stores”The server only ever sees:
- Ciphertext — the encrypted blob
- Nonce — random 12 bytes (public, used for AES-GCM)
- Auth tag — GCM authentication tag
- Wrapped keys — one per recipient, each encrypted with a different public key
- Checksum — SHA-256 of the plaintext (for integrity verification after decryption)
- Metadata — project, environment, version number, timestamp
It never sees the AES key, the plaintext, or anyone’s private key.
Key management
Section titled “Key management”SSH keys (humans)
Section titled “SSH keys (humans)”envsh uses your existing SSH keys. When you register a key, only the public key is sent to the server. Your private key never leaves your machine.
Supported key types:
- Ed25519 (recommended)
- RSA-4096 (fallback)
Machine keys (CI/CD)
Section titled “Machine keys (CI/CD)”Machine identities use Ed25519 keypairs generated by envsh. The private key is printed once at creation and never stored on the server.
Machine format: envsh-machine-v1: + base64(Ed25519 private key)
Version conflict detection
Section titled “Version conflict detection”Every push includes a base_version — the version you last pulled. If someone else pushed between your pull and push, the server rejects with a conflict error. This prevents silent overwrites.
Alice pulls v3Bob pulls v3Bob pushes v4 (base_version: 3) → successAlice pushes (base_version: 3) → CONFLICT: v4 existsAlice pulls v4, merges, pushes v5 (base_version: 4) → success