Key-at-rest encryption is an unsalted SHA-256 KDF + unauthenticated AES-256-CBC #55

Open
opened 2026-10-09 16:47:36 +00:00 by padreug · 0 comments
Owner

src/config/keys.ts:5 derives the AES key as crypto.createHash('sha256').update(passphrase).digest() — no salt, no work factor — and :7 encrypts with aes-256-cbc, no MAC/AEAD. Anyone holding nsecbunker.json (backup, leaked volume, loose perms) can brute-force the passphrase at raw-hash speed, and a tampered blob decrypts to garbage or attacker-shaped plaintext with no integrity failure. With autounlock (#16) every key on a host sits under one passphrase, so one crack = every key.

Impact: custodial-key compromise from a config-file disclosure, which is the highest-value target in the system.

Fix direction: per-key random salt + scrypt (N=2^16, NIP-49 defaults) + AES-256-GCM (or go the whole way to NIP-49 ncryptsec). Keep a read-only legacy path for existing {iv,data} blobs and re-encrypt lazily on next write, or run a one-shot backfill (cheap for us: single autounlock passphrase). Sandbox PR sandbox-team/nsecbunkerd#25 commit 1 is a working implementation with tests (tests/keys.test.ts) that can be ported nearly verbatim; call sites to touch are add.ts, start.ts, create_new_key.ts, run.ts unlockKey.

Found during reforge run #1 (sandbox nsecbunkerd#5).

`src/config/keys.ts:5` derives the AES key as `crypto.createHash('sha256').update(passphrase).digest()` — no salt, no work factor — and `:7` encrypts with `aes-256-cbc`, no MAC/AEAD. Anyone holding `nsecbunker.json` (backup, leaked volume, loose perms) can brute-force the passphrase at raw-hash speed, and a tampered blob decrypts to garbage or attacker-shaped plaintext with no integrity failure. With autounlock (#16) every key on a host sits under one passphrase, so one crack = every key. Impact: custodial-key compromise from a config-file disclosure, which is the highest-value target in the system. Fix direction: per-key random salt + scrypt (N=2^16, NIP-49 defaults) + AES-256-GCM (or go the whole way to NIP-49 `ncryptsec`). Keep a read-only legacy path for existing `{iv,data}` blobs and re-encrypt lazily on next write, or run a one-shot backfill (cheap for us: single autounlock passphrase). Sandbox PR sandbox-team/nsecbunkerd#25 commit 1 is a working implementation with tests (`tests/keys.test.ts`) that can be ported nearly verbatim; call sites to touch are `add.ts`, `start.ts`, `create_new_key.ts`, `run.ts unlockKey`. Found during reforge run #1 (sandbox nsecbunkerd#5).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/nsecbunkerd#55
No description provided.