Skip to main content

Django Tink Fields

PyPI Python Django CI

Encrypted Django model fields backed by Google Tink. Randomized AEAD fields protect confidentiality and integrity; deterministic AEAD fields additionally support exact database lookups when their equality leakage is acceptable.

Compatibility

Python Django
3.10, 3.11 5.2
3.12 5.2, 6.0
3.13, 3.14 5.2, 6.0

The package is tested against SQLite. The fields use Django's BinaryField database type and are intended to work on every database supported by Django, but applications should run their own backend-specific integration tests.

Installation

python -m pip install django-tink-fields

Configuration

Create a Tink JSON keyset, then configure its path in Django settings. A cleartext keyset contains the encryption key itself: use one only for local development or when the file is protected by controls appropriate for production secrets.

tinkey create-keyset \
  --key-template AES256_GCM \
  --out-format json \
  --out keyset.json
# settings.py
TINK_FIELDS_CONFIG = {
    "default": {
        "path": "/run/secrets/application-keyset.json",
        "cleartext": True,
    },
}

Encrypted keysets require a Tink Aead supplied by your KMS integration:

TINK_FIELDS_CONFIG = {
    "default": {
        "path": "/run/secrets/encrypted-keyset.json",
        "cleartext": False,
        "master_key_aead": kms_aead,
    },
}

Configuration and key files are loaded lazily, when a field first encrypts or decrypts a value. This allows Django to import models and serialize migrations in environments that do not hold production keys.

Usage

from django.db import models
from tink_fields import EncryptedCharField, EncryptedDateField, EncryptedEmailField


class Customer(models.Model):
    name = EncryptedCharField(max_length=100)
    email = EncryptedEmailField()
    birth_date = EncryptedDateField(null=True)

Values are ordinary Python objects on model instances. Django validates them using the corresponding built-in field's validators, encrypts them before database storage, and decrypts them when loading rows.

Randomized fields

Encrypted field Django value semantics
EncryptedBinaryField BinaryField
EncryptedBooleanField BooleanField
EncryptedCharField CharField
EncryptedDateField DateField
EncryptedDateTimeField DateTimeField
EncryptedDecimalField DecimalField
EncryptedEmailField EmailField
EncryptedFloatField FloatField
EncryptedIntegerField IntegerField
EncryptedJSONField JSONField
EncryptedPositiveIntegerField PositiveIntegerField
EncryptedSlugField SlugField
EncryptedTextField TextField
EncryptedURLField URLField
EncryptedUUIDField UUIDField

Randomized fields deliberately reject primary_key, unique, db_index, and db_default. They support isnull queries, including the equivalent field=None; every lookup that compares values raises FieldError. Database expressions such as F() assignments are also rejected because the database cannot encrypt them.

Deterministic fields and exact lookups

Generate a separate deterministic keyset and name it in settings:

tinkey create-keyset \
  --key-template AES256_SIV \
  --out-format json \
  --out deterministic-keyset.json
TINK_FIELDS_CONFIG = {
    "default": {"path": "/run/secrets/keyset.json", "cleartext": True},
    "search": {"path": "/run/secrets/deterministic-keyset.json", "cleartext": True},
}
from tink_fields import DeterministicEncryptedCharField


class ExternalIdentity(models.Model):
    subject = DeterministicEncryptedCharField(
        max_length=255,
        keyset="search",
        db_index=True,
        unique=True,
    )


identity = ExternalIdentity.objects.get(subject="stable-external-id")

Available deterministic types are Text, Char, Email, Integer, UUID, Boolean, Date, and DateTime. They support only exact and isnull lookups. db_index and unique are supported; primary keys and database defaults are not.

Deterministic encryption reveals when rows contain equal values, which can expose frequency and membership information. Do not use it for low-entropy secrets such as Boolean values, status codes, or predictable identifiers unless that leakage is explicitly acceptable. An index makes equality patterns still easier to observe.

Multiple keysets and AAD

Pass keyset to select a non-default configuration. Pass a module-level aad_callback to bind ciphertext to stable field context:

from django.db import models
from django.utils.encoding import force_bytes
from tink_fields import EncryptedCharField


def field_aad(field: models.Field) -> bytes:
    return force_bytes(f"{field.model._meta.label}:{field.name}")


class Credential(models.Model):
    secret = EncryptedCharField(
        max_length=255,
        keyset="credentials",
        aad_callback=field_aad,
    )

The callback receives the Django field, not the model instance. It must return the same bytes for every future read of existing ciphertext. Keep it at module scope so Django migrations can serialize it. Renaming a model or field will make context-derived AAD change, so plan a data migration before such a rename.

Key rotation and data migrations

Tink key rotation normally adds a new primary key while retaining old enabled keys for decryption. Replace the configured keyset atomically and restart application processes. If an immediate in-process reload is required, call:

from tink_fields import clear_keyset_cache

clear_keyset_cache()

Changing keyset= does not re-encrypt existing rows; it only changes how future reads and writes are processed. Likewise, changing an existing plaintext Django field to an encrypted field requires an explicit staged data migration. Back up data and test recovery before any key or ciphertext migration.

Security limitations

  • Losing the keyset or required master key makes data unrecoverable.
  • Exposing a cleartext keyset exposes every value encrypted with it.
  • Encryption does not hide row existence, nullness, ciphertext length, access patterns, or—when deterministic encryption is used—equality patterns.
  • Ordering encrypted columns is permitted by databases but orders ciphertext, not plaintext, and has no useful application meaning.
  • AAD authenticates context but is not secret and is not stored automatically.
  • Validation happens before storage but is not a substitute for authorization, logging controls, backups, or database security.

See SECURITY.md for vulnerability reporting and supported releases.

Development

python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev,test]" build twine pip-audit bandit tox

python -m pytest
python -m pytest -c example_project/pytest.ini example_project/example_app/tests
ruff check .
ruff format --check .
pyright --pythonpath "$(command -v python)"
tox

The release process is documented in RELEASING.md. Changes are recorded in CHANGELOG.md.

License

BSD-3-Clause. See LICENSE.txt.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_tink_fields-0.4.0.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_tink_fields-0.4.0-py3-none-any.whl (12.5 kB view details)

Uploaded Python 3

File details

Details for the file django_tink_fields-0.4.0.tar.gz.

File metadata

  • Download URL: django_tink_fields-0.4.0.tar.gz
  • Upload date:
  • Size: 17.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for django_tink_fields-0.4.0.tar.gz
Algorithm Hash digest
SHA256 30c3fed1a4643bf075242a0316088b4c7dc5f0c8b6fc82c3e1d5be870d42f62a
MD5 b90aed7478956453376d5b299cbcd56a
BLAKE2b-256 a8cb8dcab29372cc5f826a144394cc3ac49bc01b3783e53bf327a2b5464078e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_tink_fields-0.4.0.tar.gz:

Publisher: release.yml on script3r/django-tink-fields

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_tink_fields-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_tink_fields-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 972a28c74b718d3d2e2ac86dcfe23527afbed17735040c0d42d7625e28a01745
MD5 a3f3dc31246f2aad8a7d137769e888d1
BLAKE2b-256 15c8a8c18b14544b40b74062d64c4154cfcbd71b5e108a7b3b424494353df78d

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_tink_fields-0.4.0-py3-none-any.whl:

Publisher: release.yml on script3r/django-tink-fields

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.5.0

2 files

This release

0.4.0 This release

2 files

0.3.2

2 files

0.3.1

2 files

0.2.0

2 files

0.1.0

2 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