Skip to content
This repository was archived by the owner on Nov 15, 2025. It is now read-only.

lasserver/pydantic-encryption

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Encryption and Hashing Models for Pydantic

This package provides Pydantic field annotations that encrypt, decrypt, and hash field values.

Installation

Install with Pip:

pip install "pydantic_encryption[all]"

Install with Poetry:

poetry add pydantic_encryption -E all

Optional extras

  • generics: Support for generics
  • sqlalchemy: Built-in SQLAlchemy integration
  • test: Test dependencies
  • all: All optional extras

Features

  • Encrypt and decrypt specific fields
  • Hash specific fields
  • Built-in SQLAlchemy integration
  • Support for AWS KMS (Key Management Service) single-region
  • Support for Fernet symmetric encryption and Evervault
  • Support for generics

Example

from typing import Annotated
from pydantic_encryption import BaseModel, Encrypt, Hash

class User(BaseModel):
    name: str
    address: Annotated[bytes, Encrypt] # This field will be encrypted
    password: Annotated[bytes, Hash] # This field will be hashed

user = User(name="John Doe", address="123456", password="secret123")

print(user.name) # plaintext (untouched)
print(user.address) # encrypted
print(user.password) # hashed

SQLAlchemy Integration

If you install this package with the sqlalchemy extra, you can use the built-in SQLAlchemy integration for the columns.

SQLAlchemy will automatically handle the encryption/decryption of fields with the SQLAlchemyEncrypted type and the hashing of fields with the SQLAlchemyHashed type.

When you create a new instance of the model, the fields will be encrypted and when you query the database, the fields will be decrypted.

Example:

import uuid
from pydantic_encryption import SQLAlchemyEncrypted, SQLAlchemyHashed
from sqlmodel import SQLModel, Field
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# Define our schema
class User(Base, table=True):
    __tablename__ = "users"

    username: str = Field(default=None)
    email: bytes = Field(
        default=None,
        sa_type=SQLAlchemyEncrypted(),
    )
    password: bytes = Field(
        sa_type=SQLAlchemyHashed(),
        nullable=False,
    )

# Create the database
engine = create_engine("sqlite:///:memory:")
SQLModel.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

# Create a user
user = User(username="john_doe", email="john@example.com", password="secret123") # The email and password will be encrypted/hashed automatically

session.add(user)
session.commit()

# Query the user
user = session.query(User).filter_by(username="john_doe").first()

print(user.email) # decrypted
print(user.password) # hashed

Choose an Encryption Method

You can choose which encryption algorithm to use by setting the ENCRYPTION_METHOD environment variable.

Valid values are:

  • fernet: Fernet symmetric encryption
  • aws: AWS KMS
  • evervault: Evervault

See config.py for the possible environment variables.

Example:

.env

ENCRYPTION_METHOD=aws
AWS_KMS_KEY_ARN=123
AWS_KMS_REGION=us-east-1
AWS_ACCESS_KEY_ID=123
AWS_SECRET_ACCESS_KEY=123
from typing import Annotated
from pydantic_encryption import BaseModel, Encrypt

class User(BaseModel):
    name: str
    address: Annotated[bytes, Encrypt] # This field will be encrypted by AWS KMS

Default Encryption (Fernet Symmetric Encryption)

By default, Fernet will be used for encryption and decryption.

First you need to generate an encryption key. You can use the following command:

openssl rand -base64 32

Then set the following environment variable or add it to your .env file:

ENCRYPTION_KEY=your_encryption_key

Custom Encryption or Hashing

You can define your own encryption or hashing methods by subclassing SecureModel. SecureModel provides you with the utilities to handle encryption, decryption, and hashing.

self.pending_encryption_fields, self.pending_decryption_fields, and self.pending_hash_fields are dictionaries of field names to field values that need to be encrypted, decrypted, or hashed, i.e., fields annotated with Encrypt, Decrypt, or Hash.

You can override the encrypt_data, decrypt_data, and hash_data methods to implement your own encryption, decryption, and hashing logic. You then need to override model_post_init to call these methods or use the default implementation accessible via self.default_post_init().

First, define a custom secure model:

from typing import Any, override
from pydantic import BaseModel as PydanticBaseModel
from pydantic_encryption import SecureModel

class MySecureModel(PydanticBaseModel, SecureModel):
    @override
    def encrypt_data(self) -> None:
        # Your encryption logic here
        pass

    @override
    def decrypt_data(self) -> None:
        # Your decryption logic here
        pass

    @override
    def hash_data(self) -> None:
        # Your hashing logic here
        pass

    @override
    def model_post_init(self, context: Any, /) -> None:
        # Either define your own logic, for example:

        # if not self._disable:
        #     if self.pending_decryption_fields:
        #         self.decrypt_data()

        #     if self.pending_encryption_fields:
        #         self.encrypt_data()

        #     if self.pending_hash_fields:
        #         self.hash_data()

        # Or use the default logic:
        self.default_post_init()

        super().model_post_init(context)

Then use it:

from typing import Annotated
from pydantic import BaseModel # Here, we don't use the BaseModel provided by the library, but the native one from Pydantic
from pydantic_encryption import Encrypt

class MyModel(BaseModel, MySecureModel):
    username: str
    address: Annotated[bytes, Encrypt]

model = MyModel(username="john_doe", address="123456")
print(model.address) # encrypted

Encryption

You can encrypt any field by using the Encrypt annotation with Annotated and inheriting from BaseModel.

from typing import Annotated
from pydantic_encryption import Encrypt, BaseModel

class User(BaseModel):
    name: str
    address: Annotated[bytes, Encrypt] # This field will be encrypted

user = User(name="John Doe", address="123456")
print(user.address) # encrypted
print(user.name) # plaintext (untouched)

The fields marked with Encrypt are automatically encrypted during model initialization.

Decryption

Similar to encryption, you can decrypt any field by using the Decrypt annotation with Annotated and inheriting from BaseModel.

from typing import Annotated
from pydantic_encryption import Decrypt, BaseModel

class UserResponse(BaseModel):
    name: str
    address: Annotated[bytes, Decrypt] # This field will be decrypted

user = UserResponse(**user_data) # encrypted value
print(user.address) # decrypted
print(user.name) # plaintext (untouched)

Fields marked with Decrypt are automatically decrypted during model initialization.

Note: if you use SQLAlchemyEncrypted, then the value will be decrypted automatically when you query the database.

Hashing

You can hash sensitive data like passwords by using the Hash annotation.

from typing import Annotated
from pydantic_encryption import Hash, BaseModel

class User(BaseModel):
    username: str
    password: Annotated[bytes, Hash] # This field will be hashed

user = User(username="john_doe", password="secret123")
print(user.password) # hashed value

Fields marked with Hash are automatically hashed using bcrypt during model initialization.

Disable Auto Processing

You can disable automatic encryption/decryption/hashing by setting disable to True in the class definition.

from typing import Annotated
from pydantic_encryption import Encrypt, BaseModel

class UserResponse(BaseModel, disable=True):
    name: str
    address: Annotated[bytes, Encrypt]

# To encrypt/decrypt/hash, call the respective methods manually:
user = UserResponse(name="John Doe", address="123 Main St")

# Manual encryption
user.encrypt_data()
print(user.address) # encrypted

# Or user.decrypt_data() to decrypt and user.hash_data() to hash

Generics

Each BaseModel has an additional helpful method that will tell you its generic type.

To use generics, you must install this package with the generics extra: pip install pydantic_encryption[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

Install Poetry and run:

poetry install --with test
poetry run coverage run -m pytest -v -s

Roadmap

This is an early development version. I am considering the following features:

  • Add optional support for other encryption providers beyond Evervault
  • Add support for AWS KMS and other key management services
  • Native encryption via PostgreSQL and other databases
  • Specifying encryption key per table or row instead of globally

Feature Requests

If you have any feature requests, please open an issue.

About

Forked version of julien777z/pydantic-encryption

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Languages

  • Python 100.0%