aeat.adapters.persistence.storage.crypto._crypto module

AEAD primitives and HKDF derivation for at-rest persistence.

Wraps cryptography.hazmat.primitives.ciphers.aead.AESGCM and cryptography.hazmat.primitives.kdf.hkdf.HKDF behind a small, typed surface. The at-rest crypto stack pivots on this module: column-level TypeDecorator instances, the encrypted blob store, the secret store, and the schema-version envelope all consume encrypt_record(), decrypt_record(), and derive_key().

The on-wire shape of EncryptedBlob is deliberately minimal — nonce || ciphertext_with_tag. The AEAD identifier and any version metadata live in the envelope record (see adapters.persistence.storage’s EncryptionMetadata), so a future swap to e.g. ChaCha20-Poly1305 or an Argon2id-derived KEK can define a new current envelope contract without changing this primitive blob shape.

NONCE_SIZE: int

AES-256-GCM nonce size in bytes (per NIST SP 800-38D).

GCM_TAG_SIZE: int

AES-256-GCM authentication-tag size in bytes.

KEY_SIZE: int

AES-256 key size in bytes.

class EncryptedBlob(**data)[source]

Bases: BaseModel

One frozen unit of AEAD ciphertext.

The on-wire form is nonce || ciphertext_with_tag. Callers that persist the blob choose the wire encoding (raw BLOB column, base64-in-JSON, etc.). The pydantic record is the in-memory canonical form.

Variables:
  • nonce – 12 random bytes used exactly once with the wrapping key. Sourced from secrets.token_bytes(); the GCM birthday bound permits roughly 2**32 random nonces per key.

  • ciphertext – AES-256-GCM ciphertext concatenated with its 16-byte authentication tag. The min_length=GCM_TAG_SIZE permits a bare 16-byte authentication tag, which is the legitimate shape for an empty-plaintext encryption (encrypt_record(b"")); shorter values are rejected because the GCM tag would be truncated.

Parameters:
nonce: bytes
ciphertext: bytes
to_wire()[source]

Serialise the blob to the canonical nonce || ciphertext_with_tag.

Return type:

bytes

classmethod from_wire(payload)[source]

Parse the canonical wire form back into an EncryptedBlob.

Parameters:

payload (bytes) – nonce || ciphertext_with_tag. Must be at least NONCE_SIZE + GCM_TAG_SIZE bytes.

Return type:

EncryptedBlob

Returns:

The reconstructed EncryptedBlob.

Raises:

DecryptionError – If payload is shorter than the minimum envelope.

encrypt_record(plaintext, *, key, associated_data=None)[source]

Encrypt plaintext with AES-256-GCM and return an EncryptedBlob.

A 12-byte random nonce is generated for every call. The substrate’s expected throughput is far below the GCM birthday bound, so random nonces are safe.

Parameters:
  • plaintext (bytes) – Bytes to encrypt. Empty plaintext is permitted; the tag still authenticates the empty payload.

  • key (bytes) – 32-byte AES-256 key. Smaller keys are rejected.

  • associated_data (bytes | None) – Optional additional authenticated data. The same value MUST be supplied at decrypt time; mismatch raises DecryptionError.

Return type:

EncryptedBlob

Returns:

A frozen EncryptedBlob carrying the nonce and ciphertext-with-tag.

Raises:

EncryptionError – If key is not exactly KEY_SIZE bytes, or if the underlying AEAD operation fails for any other reason.

decrypt_record(blob, *, key, associated_data=None)[source]

Decrypt an EncryptedBlob and verify its authentication tag.

Parameters:
  • blob (EncryptedBlob) – The encrypted record produced by encrypt_record().

  • key (bytes) – The same 32-byte key used to encrypt the blob.

  • associated_data (bytes | None) – The same associated data passed at encrypt time. Must match exactly (including None vs b"").

Return type:

bytes

Returns:

The original plaintext bytes.

Raises:
  • DecryptionError – If the authentication tag does not verify, the ciphertext has been tampered with, the key does not match, or the associated-data binding is wrong.

  • EncryptionError – If key is not exactly KEY_SIZE bytes.

derive_key(*, key_material, salt, context, length=32)[source]

Derive a per-purpose key via HKDF-SHA256.

The substrate uses HKDF to produce per-row, per-blob, and per-store keys from a single master key. context is the HKDF info parameter and binds the derived key to a specific purpose; reusing the same master key with different context values yields cryptographically independent keys.

Parameters:
  • key_material (bytes) – The IKM (input keying material). For the substrate this is the master key.

  • salt (bytes) – HKDF salt. SHOULD be a random per-store value persisted alongside the secret-store master key file. May be empty for callers that only ever derive one key from the IKM, but a per-purpose salt is preferred.

  • context (bytes) – HKDF info parameter. SHOULD be a stable descriptive bytestring such as b"aeat.lookup.v1" or b"aeat.envelope.payload.v1".

  • length (int) – Number of bytes to derive. Defaults to KEY_SIZE.

Return type:

bytes

Returns:

length derived key bytes.

Raises:

KeyDerivationError – If the HKDF operation fails.