9 atomic tasks for alknet-secret spec conformance and gap closure, derived from architect's implementation review. Dependencies form a 5-generation graph starting with spec update, then parallel implementation tasks, ending with a review gate. Tasks address: DerivedKey zeroize security, key caching with TTL, irpc protocol integration, password derivation, secp256k1/Ethereum derivation, encryption salt/KDF, crypto test vectors, and final spec conformance review.
5.3 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| encryption-salt-kdf | Clarify and fix EncryptedData salt usage — use HKDF for key derivation or document as reserved | pending |
|
narrow | low | component | implementation |
Description
The EncryptedData struct has a salt field that is generated randomly during encryption but not used in the key derivation process. The current encryption flow is:
- Derive key from seed at path
m/74'/2'/0'/0' - Use first 32 bytes of derived private key as AES-256-GCM key
- Generate random 12-byte IV
- Generate random 32-byte salt (stored but NOT used in key derivation)
- Encrypt with AES-256-GCM using the derived key + random IV
- Store
{key_version, salt, iv, data}asEncryptedData
The salt is stored but serves no purpose. This is a gap because:
- Without a KDF, the same derived key is used for every encryption operation (different IVs provide per-message randomness, but the key itself is static)
- Salt-based key derivation would add an additional security layer: even if the derivation path is known, the salt provides per-encryption diversity
- The
key_versionfield exists for rotation but without KDF-based key derivation, there's no mechanism to rotate to a stronger key
The spec update (spec-update-secret-service task) decides one of two paths:
Option A: Use HKDF for key derivation (recommended for v1)
Replace the direct "first 32 bytes of derived key" approach with:
- Derive master key from seed at path
m/74'/2'/0'/0' - Use HKDF-SHA256 with
saltandinfo = "alknet-encryption-v{key_version}"to derive the actual AES-256-GCM key - This means: same seed + same path + different salt = different AES key
Benefits: Each encryption uses a unique derived key (even with the same master key), providing forward security and key diversity. The salt is now purposeful.
Option B: Document salt as reserved (Phase B)
Keep the current approach (direct key from derivation path) and document the salt field as "reserved for future KDF-based key derivation." Add a comment explaining that v1 doesn't use the salt.
This is simpler in v1 but defers the security improvement.
This task implements whichever option the spec update chooses. If the spec says "use HKDF now," implement Option A. If it says "document as reserved," implement Option B.
If Option A (HKDF):
- Add
hkdfdependency toCargo.toml - Modify
encryption::encrypt():- Generate random salt (32 bytes)
- Use HKDF-SHA256 to derive AES key from:
master_key + salt + info - The
infostring includes the key version for forward compatibility
- Modify
encryption::decrypt():- Use HKDF-SHA256 with the stored salt to re-derive the AES key
- Decrypt ciphertext with the derived key + stored IV
- Backward compatibility: Add an
EncryptedData::versionor check if salt is empty/all-zeros to detect v1 (direct key) vs v2 (HKDF) format. Or, since key_version=1 is already in use, bump key_version to 2 for HKDF-derived keys and support both in decrypt.
If Option B (reserved):
- Add documentation/comments to
encryption.rsandEncryptedDataexplaining that the salt is reserved for future KDF - Add a
// TODO(Phase B)comment on the salt generation - No code behavior changes
Acceptance Criteria
If Option A (HKDF — recommended):
hkdfdependency added toCargo.tomlencrypt()uses HKDF-SHA256 withsalt + info = "alknet-encryption-v{key_version}"to derive AES keydecrypt()uses HKDF-SHA256 with storedsaltto re-derive AES keyEncryptedDatawithkey_version >= 2uses HKDFEncryptedDatawithkey_version == 1uses direct key (backward compat)- Backward compatibility: data encrypted with v1 format can still be decrypted
CURRENT_KEY_VERSIONbumped to 2- Unit test: encrypt/decrypt round-trip with HKDF (key_version 2)
- Unit test: decrypt v1-encrypted data (direct key) still works
- Unit test: different salts produce different ciphertext keys (even with same master key)
EncryptionKeystruct updated to carry HKDF info if needed
If Option B (reserved):
encryption.rshas documentation explaining salt is reserved for future KDFEncryptedDatastruct has doc comment onsaltfield explaining reserved purpose// TODO(Phase B)comment on salt generation inencrypt()- No behavior changes — existing tests pass unchanged
References
- docs/architecture/secret-service.md — Encryption section (after spec update)
- crates/alknet-secret/src/encryption.rs — Current encrypt/decrypt implementation
- HKDF (RFC 5869): https://tools.ietf.org/html/rfc5869
Notes
My recommendation is Option A (HKDF). It's a small amount of additional code (the
hkdfcrate is tiny and well-tested), it makes thesaltfield purposeful, and it provides per-encryption key diversity. The backward compatibility concern is manageable: decrypt based onkey_version(v1 = direct, v2 = HKDF).
The architect's message specifically called out: "The EncryptedData struct has a salt field but the encryption function generates a random salt per encryption without using it for key derivation. Either the salt should be used in a KDF, or the field should be documented as reserved." This task resolves that ambiguity.
Summary
To be filled on completion