aeat.adapters.persistence.storage.master_key._recovery module¶
Recovery-key generation, encoding, and master-key wrapping.
The recovery-key story for the secure-persistence substrate is two- fold:
Mnemonic encoding: a 32-byte recovery key is encoded as a 24-word BIP-39 English mnemonic for human-readability. The operator prints the mnemonic at provision time and stores it somewhere safe; lost passphrase / lost keychain implies recovery via this mnemonic. The substrate never persists the mnemonic on disk.
Recovery-key wrapping: at provision time, the master key is wrapped under an HKDF-SHA256-derived KEK seeded by the recovery key, and the wrapped ciphertext is persisted as
master.recovery.key. When the active master-key provider becomes unavailable (forgotten passphrase, locked keychain, broken keyring), operator key-management code supplies the recovery mnemonic and the substrate uses the wrapping to mint a freshmaster.key+master.kdf+salttriplet under the chosen new backend.
This module exports the cryptographic primitives only; command wiring must remain outside the storage substrate.
The encoding follows BIP-39 (Bitcoin Improvement Proposal 0039)
exactly — 256-bit entropy → 8-bit checksum → 24 11-bit words drawn
from the canonical English wordlist. The wordlist is bundled at
_bip39_wordlist.txt (2048 lines, public domain, identical
to the Bitcoin Core source).
- class RecoveryKey(**data)[source]¶
Bases:
BaseModelFrozen container for a 32-byte recovery key + its 24-word mnemonic.
The substrate never persists the raw bytes or the mnemonic; this record exists in memory only at provision time so the CLI can display the mnemonic and the wrapping helper can derive a KEK from the bytes. After display + wrap, both fields are dropped from memory by Python’s garbage collection.
- raw: bytes¶
- mnemonic: str¶
- class WrappedMasterKey(**data)[source]¶
Bases:
BaseModelFrozen container for the recovery-key-wrapped master.key file.
Persisted as JSON at
<secret_store_dir>/master.recovery.key. The operator’s recovery key is the only material that can unwrap this file; the substrate’s regular providers (keyring, file-fallback, unsecured) cannot consume it.The nonce and ciphertext are stored as base64 strings (rather than raw
bytes) so the JSON serialisation is portable across pydantic versions and operating systems. Theto_blob/from_blobhelpers convert back to the in-memoryEncryptedBlobform.- schema_version: int¶
- nonce_b64: str¶
- ciphertext_b64: str¶
- to_blob()[source]¶
Decode the base64 fields into an
EncryptedBlob.- Return type:
- classmethod from_blob(blob)[source]¶
Build a
WrappedMasterKeyfrom an in-memory blob.- Return type:
- Parameters:
blob (EncryptedBlob)
- encode_mnemonic(entropy)[source]¶
Encode 32 bytes of entropy as a 24-word BIP-39 English mnemonic.
- Parameters:
entropy (
bytes) – Exactly 32 bytes of cryptographic entropy.- Return type:
- Returns:
A space-joined string of 24 lowercase English words.
- Raises:
StorageValidationError – When
entropyis not exactly 32 bytes.
- decode_mnemonic(mnemonic)[source]¶
Decode a 24-word BIP-39 English mnemonic back into 32 bytes of entropy.
- Parameters:
mnemonic (
str) – A space-separated string of exactly 24 words from the BIP-39 English wordlist.- Return type:
- Returns:
The 32-byte entropy.
- Raises:
StorageValidationError – When the mnemonic does not have 24 words, contains an unknown word, or fails the BIP-39 checksum.
- generate_recovery_key()[source]¶
Mint a fresh
RecoveryKeywith 32-byte entropy and its 24-word mnemonic.Uses
secrets.token_bytes()for the entropy. The returned record is the only in-memory copy; callers must arrange for the operator to copy or print the mnemonic before the record falls out of scope.- Return type:
- wrap_master_key(*, master_key, recovery_key)[source]¶
Wrap a 32-byte master key under a recovery-key-derived KEK.
- Parameters:
master_key (
bytes) – The 32-byte master key to wrap.recovery_key (
RecoveryKey) – The recovery key whose bytes seed the wrapping KEK.
- Return type:
- Returns:
A
WrappedMasterKeycarrying the 12-byte nonce + the AES-256-GCM ciphertext. Serialise viamodel_dump_json()and persist tomaster.recovery.key.- Raises:
StorageValidationError – When
master_keyis not exactly 32 bytes.
- unwrap_master_key(*, wrapped, recovery_key_bytes)[source]¶
Recover the 32-byte master key from a wrapped record + the recovery-key bytes.
- Parameters:
wrapped (
WrappedMasterKey) – TheWrappedMasterKeyloaded from disk.recovery_key_bytes (
bytes) – The 32-byte recovery key (decoded viadecode_mnemonic()).
- Return type:
- Returns:
The 32-byte master key.
- Raises:
StorageValidationError – When
recovery_key_bytesis not exactly 32 bytes.
- save_wrapped_master_key(wrapped, path)[source]¶
Atomically persist a wrapped master key to
path.Uses the substrate’s
atomic_write_secure_byteshelper so the file lands restricted from creation (mode 0o600), the tempfile is fsynced before theos.replaceswap, and the parent directory entry is fsynced after — durable across power loss on POSIX.- Return type:
- Parameters:
wrapped (WrappedMasterKey)
path (Path)
- load_wrapped_master_key(path)[source]¶
Read and validate a wrapped-master-key file, returning a
WrappedMasterKey.- Return type:
- Parameters:
path (Path)