Encryption and Hashing Models for Pydantic
Project description
Encryption and Hashing Models for Pydantic
Field-level encryption, decryption, hashing, and blind indexing for Pydantic models with SQLAlchemy integration.
Installation
pip install pydantic_encryption
Optional extras
pip install "pydantic_encryption[sqlalchemy]" # SQLAlchemy integration
pip install "pydantic_encryption[aws]" # AWS KMS encryption
pip install "pydantic_encryption[all]" # All optional dependencies
Quick Start
from typing import Annotated
from pydantic_encryption import BaseModel, Encrypt, Hash
class User(BaseModel):
name: str
address: Annotated[bytes, Encrypt]
password: Annotated[bytes, Hash]
user = User(name="John Doe", address="123456", password="secret123")
print(user.name) # plaintext
print(user.address) # encrypted
print(user.password) # hashed
Fields marked with Encrypt are encrypted and fields marked with Hash are hashed during model initialization.
To decrypt, use the Decrypt annotation:
from pydantic_encryption import Decrypt, BaseModel
class UserResponse(BaseModel):
address: Annotated[str, Decrypt]
user = UserResponse(address=encrypted_bytes)
print(user.address) # decrypted
Encryption Methods
Set the encryption method via environment variable:
ENCRYPTION_METHOD=fernet # Fernet symmetric encryption (requires ENCRYPTION_KEY)
ENCRYPTION_METHOD=aws # AWS KMS (requires AWS_KMS_KEY_ARN, AWS_KMS_REGION, etc.)
ENCRYPTION_METHOD=evervault # Evervault
There is no default — you must explicitly set ENCRYPTION_METHOD if using Encrypt/Decrypt fields.
Fernet Setup
# Generate a key
openssl rand -base64 32
# Set environment variables
ENCRYPTION_METHOD=fernet
ENCRYPTION_KEY=your_generated_key
AWS KMS Setup
ENCRYPTION_METHOD=aws
AWS_KMS_KEY_ARN=arn:aws:kms:us-east-1:123456789:key/your-key-id
AWS_KMS_REGION=us-east-1
AWS_KMS_ACCESS_KEY_ID=your_access_key
AWS_KMS_SECRET_ACCESS_KEY=your_secret_key
Separate encrypt/decrypt keys are supported for key rotation or read-only scenarios:
AWS_KMS_ENCRYPT_KEY_ARN=arn:aws:kms:...encrypt-key
AWS_KMS_DECRYPT_KEY_ARN=arn:aws:kms:...decrypt-key
See config.py for all environment variables.
SQLAlchemy Integration
Install with pip install "pydantic_encryption[sqlalchemy]".
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from pydantic_encryption.integrations.sqlalchemy import (
SQLAlchemyEncryptedValue,
SQLAlchemyHashed,
SQLAlchemyBlindIndexValue,
)
from pydantic_encryption.types import BlindIndexMethod
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str]
email: Mapped[bytes] = mapped_column(SQLAlchemyEncryptedValue())
password: Mapped[bytes] = mapped_column(SQLAlchemyHashed())
blind_index_email: Mapped[bytes] = mapped_column(
SQLAlchemyBlindIndexValue(BlindIndexMethod.HMAC_SHA256)
)
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
user = User(
username="john",
email="john@example.com",
password="secret123",
blind_index_email="john@example.com",
)
session.add(user)
session.commit()
# Query by blind index — automatically hashed
found = session.query(User).filter(
User.blind_index_email == "john@example.com"
).first()
print(found.email) # decrypted
SQLAlchemyBlindIndexValue supports the same normalization options as BlindIndex:
blind_index_email: Mapped[bytes] = mapped_column(
SQLAlchemyBlindIndexValue(
BlindIndexMethod.HMAC_SHA256,
normalize_to_lowercase=True,
strip_whitespace=True,
)
)
Supported Types
SQLAlchemyEncryptedValue preserves the Python type of your data:
str, bytes, bool, int, float, Decimal, UUID, date, datetime, time, timedelta
Array Support (PostgreSQL)
from pydantic_encryption.integrations.sqlalchemy import SQLAlchemyPGEncryptedArray
tags: Mapped[list[str] | None] = mapped_column(SQLAlchemyPGEncryptedArray(), nullable=True)
Each element is individually encrypted. Requires PostgreSQL.
Blind Indexes
Blind indexes enable equality searches on encrypted data by storing a deterministic keyed hash alongside the ciphertext.
Configuration: Set BLIND_INDEX_SECRET_KEY via environment variable.
Pydantic Models
from typing import Annotated
from pydantic_encryption import BaseModel, BlindIndex, BlindIndexMethod
class User(BaseModel):
email_index: Annotated[bytes, BlindIndex(BlindIndexMethod.HMAC_SHA256)]
Normalization
You can normalize values before hashing to ensure consistent lookups:
email_index: Annotated[bytes, BlindIndex(
BlindIndexMethod.HMAC_SHA256,
normalize_to_lowercase=True,
strip_whitespace=True,
)]
Available options:
| Option | Effect |
|---|---|
strip_whitespace |
Strip leading/trailing whitespace, collapse internal whitespace |
strip_non_characters |
Remove all non-letter characters (keep only a-zA-Z) |
strip_non_digits |
Remove all non-digit characters (keep only 0-9) |
normalize_to_lowercase |
Convert to lowercase |
normalize_to_uppercase |
Convert to uppercase |
Methods
| Method | Description |
|---|---|
BlindIndexMethod.HMAC_SHA256 |
Fast HMAC-SHA256 keyed hash. Standard choice. |
BlindIndexMethod.ARGON2 |
Memory-hard Argon2 hash with deterministic salt. Better brute-force resistance. |
Disable Auto-Processing
class UserResponse(BaseModel, disable=True):
address: Annotated[bytes, Encrypt]
user = UserResponse(address="123 Main St")
user.encrypt_data() # manual encryption
Custom Encryption or Hashing
Subclass SecureModel to implement your own logic:
from pydantic import BaseModel as PydanticBaseModel
from pydantic_encryption import SecureModel
class MySecureModel(PydanticBaseModel, SecureModel):
def encrypt_data(self) -> None:
# your encryption logic
pass
def model_post_init(self, context, /):
self.default_post_init()
super().model_post_init(context)
Generics
from pydantic_encryption import BaseModel
class MyModel[T](BaseModel):
value: T
model = MyModel[str](value="Hello")
print(model.get_type()) # <class 'str'>
Run Tests
poetry install --all-extras
poetry run pytest -v
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pydantic_encryption-0.5.1.tar.gz.
File metadata
- Download URL: pydantic_encryption-0.5.1.tar.gz
- Upload date:
- Size: 14.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c472b7bc0ba8ff23573911466f70f6369837f8ca7ee6beaa5ece56ca9befdcf
|
|
| MD5 |
460db17d7f47e85ff8ff59dbfb6ff74f
|
|
| BLAKE2b-256 |
a857de3bd5f45f0422abcfcb6e6f5fb68de582bad12d2e194ef063958748b9ea
|
Provenance
The following attestation bundles were made for pydantic_encryption-0.5.1.tar.gz:
Publisher:
publish-to-pypi.yml on julien777z/pydantic-encryption
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pydantic_encryption-0.5.1.tar.gz -
Subject digest:
4c472b7bc0ba8ff23573911466f70f6369837f8ca7ee6beaa5ece56ca9befdcf - Sigstore transparency entry: 1281947971
- Sigstore integration time:
-
Permalink:
julien777z/pydantic-encryption@0974edb73eb7c13e058eac2cfa5a9c6d877f3593 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/julien777z
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@0974edb73eb7c13e058eac2cfa5a9c6d877f3593 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pydantic_encryption-0.5.1-py3-none-any.whl.
File metadata
- Download URL: pydantic_encryption-0.5.1-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ccfafa850e35e62525d38533472ffcfc0eabfbd7a0fd07d174a53d156348184
|
|
| MD5 |
046a4bbe491c5279e469e5092fd31423
|
|
| BLAKE2b-256 |
9fc20cb94ee308c8ece9867401403573916bd93af29362b60401eab12036a322
|
Provenance
The following attestation bundles were made for pydantic_encryption-0.5.1-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on julien777z/pydantic-encryption
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pydantic_encryption-0.5.1-py3-none-any.whl -
Subject digest:
9ccfafa850e35e62525d38533472ffcfc0eabfbd7a0fd07d174a53d156348184 - Sigstore transparency entry: 1281948060
- Sigstore integration time:
-
Permalink:
julien777z/pydantic-encryption@0974edb73eb7c13e058eac2cfa5a9c6d877f3593 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/julien777z
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@0974edb73eb7c13e058eac2cfa5a9c6d877f3593 -
Trigger Event:
workflow_dispatch
-
Statement type: