Skip to main content

sqlalchemy-encrypted-field

Encrypted column types for SQLAlchemy. Secrets are ciphertext at rest, plaintext only where you ask for it.

from sqlalchemy_encrypted_field import EncryptedTextField, configure

configure(os.environ["SQLALCHEMY_ENCRYPTED_FIELD_KEYS"])


class Server(Base):
    __tablename__ = "servers"

    id: Mapped[int] = mapped_column(primary_key=True)
    token = EncryptedTextField(default="")


session.add(Server(token="sk-live-..."))
session.commit()

server.token  # Secret(<redacted>)
server.token.decrypt()  # "sk-live-..."

What it does differently

Every envelope is bound to its column. The associated data is the owning table.column, so ciphertext copied from one column into another fails to decrypt. Without this, anyone with write access to the database can move a low-value token into a high-value column, or one tenant's row into another's, without ever touching a key.

Key rotation has no flag day. The key setting is a comma-separated list. The first key encrypts, every key decrypts. Rotating means prepending a new key and dropping the old one once nothing needs it, with no downtime and no bulk re-encryption.

Loading a row never decrypts it. Reads hand back a Secret, not a string. Decryption happens only where decrypt() is called, and repr() is redacted, so a logged row or a stack trace cannot leak the value.

Failures are named. A dropped key raises SecretDecryptError, whose message points at the key configuration rather than a bare crypto traceback. Using a column before configure() raises NotConfiguredError instead of silently storing plaintext.

Install

pip install sqlalchemy-encrypted-field

Requires Python 3.11+, SQLAlchemy 2.0+. Tested on 3.11 through 3.14.

Fields

Field Assign Read back
EncryptedTextField() str Secret, use .decrypt() -> str
EncryptedBytesField() bytes SecretBytes, use .decrypt() -> bytes
EncryptedJsonField() dict SecretsMapping, a lazily-decrypting dict

All three store LargeBinary and are NOT NULL. Empty values ("", b"") are stored as empty bytes with no envelope, so a missing secret costs no crypto and needs no key.

SecretsMapping tracks in-place writes, so row.data["token"] = "new" marks the column dirty and persists on flush. Reach for it when a secret is genuinely a mapping; a single value belongs in an EncryptedTextField.

Assigning a loaded Secret to a different column re-encrypts it under that column's associated data, so moving a value between columns works.

Keys

Generate one:

python -c "import base64, os; print(base64.b64encode(os.urandom(32)).decode())"

configure() takes that string, or a comma-separated list of them. It also takes a callable returning the string, which is consulted on every operation. Use the callable form when keys come from settings that can be reloaded at runtime.

configure(lambda: settings.secrets_key)

validate_keys(value) is exposed separately so the same rules can run inside your own settings validation and fail at startup. A malformed or absent key is always an error, never a fallback to plaintext. Validation errors never echo the key material, because they end up in logs.

Envelope

version[1] || salt[16] || nonce[12] || ciphertext[n] || tag[16]

AES-256-GCM. The salt derives a per-envelope key with HKDF-SHA256 over the configured master key, so the master never reaches a cipher directly and nonce reuse across envelopes is not a concern. The table.column string is the associated data.

configure(..., info=b"...") overrides the HKDF info string. The default suits new projects. Set it only to keep reading envelopes written by an existing store that used a different one, and never change it once data exists, because it is part of the key derivation.

What it is not

This encrypts values so that a database dump, a backup, or a replica is not a pile of readable credentials. It does not protect against an attacker who can read your process memory or your key.

License

MIT

Metadata

Release files for sqlalchemy-encrypted-field 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sqlalchemy-encrypted-field 1.0.2
File Size Uploaded
sqlalchemy_encrypted_field-1.0.2.tar.gz 11.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sqlalchemy-encrypted-field 1.0.2
File Interpreter ABI Platform
sqlalchemy_encrypted_field-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 20.7 kB

Release files / sqlalchemy_encrypted_field-1.0.2.tar.gz

Download URL sqlalchemy_encrypted_field-1.0.2.tar.gz
Size 11.0 kB
Tags Source
SHA-256 checksum
How to use checksums
5ea80fd66ddc902e1f0f533822f0c760cba91f20b8e7817ea040a015053c117e
BLAKE2b-256 checksum
How to use checksums
8f81c61f39d4a2846fea3f2bd33a2907a7ae7f0cf6419dabc8e4a04821eb57ae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release files / sqlalchemy_encrypted_field-1.0.2-py3-none-any.whl

Download URL sqlalchemy_encrypted_field-1.0.2-py3-none-any.whl
Size 9.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
83f47f32b092ce8c3b4cb38cc29fb66145b8ce73273caf8995b270becab5a302
BLAKE2b-256 checksum
How to use checksums
c2f2d547c79c46e2448201bb100bad3da633c375a8367840bd3ef185d722fb02
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 release files

1.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page