SQLAlchemy encrypted columns¶
This guide covers EncryptedString in gemstone_utils.sqlalchemy.encrypted_type: transparent encryption on write, lazy decryption on read, and how it interacts with KeyContext and key ids.
What EncryptedString does¶
Write path (
process_bind_param): Plaintext is encrypted with the currentKeyContext(EncryptedString.set_current_keyctx). Already-encrypted strings are rejected to avoid double encryption.Read path (
process_result_value): The stored wire is parsed; segment 2 is the logical DEK id (UUID string). AKeyContextfor that id is obtained from the resolver (EncryptedString.set_keyctx_resolver). The result is aLazySecretthat decrypts when you access the value.
EncryptedString does not manage database migrations, key storage, or passphrase handling. For persisted keys, see key-storage.md.
Initialization order¶
Before any read or write of encrypted columns:
EncryptedString.set_current_keyctx(ctx)—KeyContextused for new writes (plaintext → ciphertext).ctx.keyidshould be a new UUID fromgemstone_utils.key_id.new_key_id()when provisioning a new DEK.EncryptedString.set_keyctx_resolver—Callable[[str], KeyContext]. Given the UUID string from segment 2 of stored ciphertext, return theKeyContextthat can decrypt that row.
Writes only need the current key. Reads need the resolver to map historical key ids (rotation, multiple DEKs).
Key ids are strings (UUIDs)¶
KeyContext.keyid and the resolver argument are str, canonical UUID text (typically UUIDv7 from new_key_id()). Integer key ids in legacy data are not accepted by current parsers; see release-notes.md and key-storage.md.
Rotation behavior¶
New rows use
set_current_keyctxwith the active DEK.Old rows still contain ciphertext whose segment 2 is an older DEK id. The resolver must return the correct
KeyContextfor each id until those rows are re-encrypted or migrated.
Failure modes¶
Symptom |
Typical cause |
|---|---|
|
Write attempted before configuration. |
|
Read attempted before resolver. |
|
Assigning an already-wired ciphertext as plaintext. |
|
Unknown |