Fernet-encrypted model fields for Django
django-fernet-fields-v2 provides Fernet symmetric encryption for Django model fields, leveraging the `cryptography` library. It is a fork of `django-fernet-fields`, specifically updated to support Django 4 and later versions, as well as Python 3.8+. The current version is 0.9, released in August 2023, with releases typically occurring on an as-needed basis to maintain compatibility.
Warnings
- breaking Loss of encryption keys will result in permanent data loss. If the `SECRET_KEY` (or any key in `FERNET_KEYS`) used to encrypt data is lost, that data becomes irrecoverable.
- gotcha Fernet encryption is non-deterministic, meaning the same plaintext encrypts to different ciphertext each time. This makes database indexing, unique constraints, and direct lookups (e.g., `filter(field='value')`) on encrypted fields impossible or meaningless, and can raise `django.core.exceptions.ValidationError` or similar errors if attempted.
- gotcha When rotating encryption keys using `FERNET_KEYS`, new data will be encrypted with the first key in the list, but old data must be manually re-encrypted if you want it to use the new key for future saves. Simply changing `FERNET_KEYS` will not automatically re-encrypt existing data in the database.
- gotcha Encrypted data is generally longer than its plaintext counterpart. If you use `FernetCharField`, you must ensure `max_length` is sufficient to accommodate the encrypted string, otherwise data truncation or validation errors may occur.
- gotcha Using nullable encrypted fields (`blank=True, null=True`) can inadvertently leak information about the presence or absence of data, as a `NULL` in the database trivially indicates an empty field.
Install
-
pip install django-fernet-fields-v2
Imports
- FernetCharField
from fernet_fields import FernetCharField
- FernetTextField
from fernet_fields import FernetTextField
- FernetEmailField
from fernet_fields import FernetEmailField
- FernetIntegerField
from fernet_fields import FernetIntegerField
- FernetDateField
from fernet_fields import FernetDateField
- FernetDateTimeField
from fernet_fields import FernetDateTimeField
Quickstart
import os
from django.db import models
from django.conf import settings
from fernet_fields import FernetTextField
# Minimum Django settings for model use
settings.configure(
SECRET_KEY=os.environ.get('DJANGO_SECRET_KEY', 'a-very-secret-key-for-testing-only-do-not-use-in-prod'),
DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}},
INSTALLED_APPS=['fernet_fields'],
)
# Define a model with an encrypted text field
class MyEncryptedModel(models.Model):
secret_data = FernetTextField()
def __str__(self):
return f"Encrypted ID: {self.id}"
# Example usage (after migrations, typically via manage.py)
# In a real application, you would run makemigrations and migrate.
# For this quickstart, we'll simulate it for demonstration.
if not settings.configured:
settings.configure(DEBUG=True, SECRET_KEY='dummy-key', INSTALLED_APPS=['fernet_fields'])
# Note: In a real Django project, you would create and apply migrations.
# For a standalone runnable example, we bypass direct model setup.
# from django.apps import apps; apps.populate(settings.INSTALLED_APPS)
# This part requires a proper Django setup with migrations applied.
# For demonstration, assume model and database are ready.
# from django.db import connection
# with connection.schema_editor() as schema_editor:
# schema_editor.create_model(MyEncryptedModel)
# Demonstrate field usage (conceptually)
# If MyEncryptedModel was properly migrated:
# instance = MyEncryptedModel.objects.create(secret_data='This is highly sensitive information.')
# print(f"Saved encrypted data. Retrieved: {instance.secret_data}")
# print(f"Data in DB (would be encrypted): {MyEncryptedModel.objects.get(id=instance.id).secret_data}")
print("To run this, integrate into a Django project, define FERNET_KEYS or SECRET_KEY, and apply migrations.")
print("Example model `MyEncryptedModel` defined with `secret_data = FernetTextField()`")