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:

  1. 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.

  2. 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 fresh master.key + master.kdf + salt triplet 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: BaseModel

Frozen 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.

Parameters:
raw: bytes
mnemonic: str
class WrappedMasterKey(**data)[source]

Bases: BaseModel

Frozen 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. The to_blob / from_blob helpers convert back to the in-memory EncryptedBlob form.

Parameters:
  • schema_version (int)

  • nonce_b64 (str)

  • ciphertext_b64 (str)

schema_version: int
nonce_b64: str
ciphertext_b64: str
to_blob()[source]

Decode the base64 fields into an EncryptedBlob.

Return type:

EncryptedBlob

classmethod from_blob(blob)[source]

Build a WrappedMasterKey from an in-memory blob.

Return type:

WrappedMasterKey

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:

str

Returns:

A space-joined string of 24 lowercase English words.

Raises:

StorageValidationError – When entropy is 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:

bytes

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 RecoveryKey with 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:

RecoveryKey

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:

WrappedMasterKey

Returns:

A WrappedMasterKey carrying the 12-byte nonce + the AES-256-GCM ciphertext. Serialise via model_dump_json() and persist to master.recovery.key.

Raises:

StorageValidationError – When master_key is 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:
Return type:

bytes

Returns:

The 32-byte master key.

Raises:

StorageValidationError – When recovery_key_bytes is 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_bytes helper so the file lands restricted from creation (mode 0o600), the tempfile is fsynced before the os.replace swap, and the parent directory entry is fsynced after — durable across power loss on POSIX.

Return type:

None

Parameters:
load_wrapped_master_key(path)[source]

Read and validate a wrapped-master-key file, returning a WrappedMasterKey.

Return type:

WrappedMasterKey

Parameters:

path (Path)