Django Fernet Encrypted Fields
django-fernet-encrypted-fields provides symmetrically encrypted model fields for Django, leveraging Fernet encryption from the `cryptography` library. It ensures that data is encrypted before being stored in the database and automatically decrypted when accessed in the application. This library is actively maintained as part of the Jazzband project, with recent updates and a focus on security for sensitive data at rest.
Common errors
-
InvalidToken / Cannot decrypt data
cause The encryption key (`SALT_KEY` or `SECRET_KEY`) used to decrypt the data is different from the key used to encrypt it, or the data has been corrupted.fixVerify that your `SALT_KEY` (or `SECRET_KEY`) in `settings.py` or environment variables matches the key used when the data was originally saved. Ensure no data corruption occurred. For key rotation, ensure all valid keys are provided in the `SALT_KEY` list or `SECRET_KEY_FALLBACKS` (Django >= 4.1). -
django.core.exceptions.ImproperlyConfigured: EncryptedField cannot be indexed.
cause An `EncryptedField` (or its subclasses) has `db_index=True`, `unique=True`, or `primary_key=True` set.fixRemove `db_index=True`, `unique=True`, and `primary_key=True` from the `EncryptedField` definition in your model. Encrypted data is not suitable for these database constraints. -
incorrect padding
cause The Fernet key provided (either `SALT_KEY` or `SECRET_KEY` if `SALT_KEY` is not used) is not a valid 32-bit URL-safe base64-encoded bytestring, or the data is corrupted. This can happen if `FERNET_USE_HKDF = False` (from related libraries) and the key is not correctly formatted.fixEnsure your `SALT_KEY` (or `SECRET_KEY`) is a correctly generated Fernet-compatible key. When `SALT_KEY` is used, the library handles the HKDF derivation, so ensure `SALT_KEY` is a strong, random string. If manually managing Fernet keys (not common with this library), generate them using `Fernet.generate_key()` from `cryptography`. -
Data too long for field (e.g., 'value too long for type character varying(X)')
cause Encrypted data is typically longer than the original plaintext. The `max_length` of the underlying `CharField` might be insufficient.fixIncrease the `max_length` attribute for `EncryptedCharField` instances to accommodate the increased size of encrypted data. `EncryptedTextField` does not have a `max_length` limit, making it suitable for longer encrypted strings.
Warnings
- breaking Changing an existing unencrypted field to an encrypted field, or vice-versa, requires a complex three-step data migration: add a new field (nullable), copy data using a data migration (which encrypts/decrypts), then remove the old field and optionally rename the new one. Direct field type changes in `models.py` will result in data loss or unreadable data.
- gotcha Fernet encryption is non-deterministic, meaning the same plaintext encrypts to a different ciphertext each time. This makes encrypted fields unsuitable for database indexing (`db_index=True`, `unique=True`, `primary_key=True`), lookups (other than `isnull`), or meaningful ordering, as these operations would be performed on the unhelpful ciphertext. Setting `db_index=True`, `unique=True`, or `primary_key=True` will raise a `django.core.exceptions.ImproperlyConfigured` error.
- breaking Loss of the `SALT_KEY` (or `SECRET_KEY` if `SALT_KEY` is not set) will render all encrypted data irrecoverable. The security of your encrypted data is entirely dependent on the secrecy and retention of this key.
- gotcha Nullable encrypted fields (`null=True`) trivially reveal the presence or absence of data to an attacker. If this is a concern, avoid nullable encrypted fields.
- gotcha When deploying to platforms like Heroku, explicit pinning of `cryptography` and its underlying C library `libffi` might be required in `requirements.txt` to ensure proper build and deployment.
Install
-
pip install django-fernet-encrypted-fields
Imports
- EncryptedCharField
from encrypted_fields.fields import EncryptedCharField
- EncryptedTextField
from encrypted_fields.fields import EncryptedTextField
- EncryptedIntegerField
from encrypted_fields.fields import EncryptedIntegerField
- EncryptedEmailField
from encrypted_fields.fields import EncryptedEmailField
- * (wildcard import)
from encrypted_fields.fields import *
from encrypted_fields.fields import EncryptedTextField
- EncryptedTextField (from old package)
from fernet_fields import EncryptedTextField
from encrypted_fields.fields import EncryptedTextField
Quickstart
import os
from django.db import models
from encrypted_fields.fields import EncryptedTextField
# In your Django settings.py file, define SALT_KEY
# For production, load from environment variables and ensure it's a strong, random string.
# Example for settings.py (not for production directly):
# import os
# os.environ.setdefault('DJANGO_SALT_KEY', '0123456789abcdefghijklmnopqrstuvwxyz')
SALT_KEY = os.environ.get('DJANGO_SALT_KEY', 'a_default_32_char_salt_key_for_dev')
# For Django >= 4.1, you can also use SECRET_KEY_FALLBACKS for SECRET_KEY rotation.
# SECRET_KEY_FALLBACKS = [os.environ.get('OLD_DJANGO_SECRET_KEY', '')]
class MyEncryptedModel(models.Model):
sensitive_data = EncryptedTextField()
name = models.CharField(max_length=255)
def __str__(self):
return self.name
# Example usage (assuming Django setup and migrations are run):
# from myapp.models import MyEncryptedModel
# instance = MyEncryptedModel.objects.create(name='Test User', sensitive_data='This is a secret message.')
# print(instance.sensitive_data) # Automatically decrypted: 'This is a secret message.'
# print(instance.pk)