Django Encrypted Model Fields
django-encrypted-model-fields provides a set of Django model fields that encrypt data before saving it to the database, leveraging the robust `cryptography` library. It's currently at version 0.6.5 and typically follows Django's release cycle for compatibility, with more frequent updates for bug fixes or features. It ensures data at rest is protected.
Common errors
-
django.core.exceptions.ImproperlyConfigured: ENCRYPTED_FIELD_KEYS setting must be a dictionary or a string.
cause `ENCRYPTED_FIELD_KEYS` is missing from `settings.py` or is defined with an incorrect type (e.g., `None` or an empty tuple).fixAdd `ENCRYPTED_FIELD_KEYS = {'default': 'your_secret_key'}` to your `settings.py`. Ensure it's a dictionary for multiple keys or a single string for a global default. -
encrypted_model_fields.exceptions.MissingKeyError: Key 'my_custom_key' not found in ENCRYPTED_FIELD_KEYS
cause An `EncryptedField` was defined with `key_name='my_custom_key'`, but 'my_custom_key' is not present as a key in the `ENCRYPTED_FIELD_KEYS` dictionary in `settings.py`.fixEnsure that every `key_name` used in your `EncryptedField` definitions has a corresponding entry in `ENCRYPTED_FIELD_KEYS` in `settings.py`. For example, add `'my_custom_key': 'value_for_custom_key'` to the dictionary. -
ValueError: Ciphertext failed verification
cause This error typically originates from the `cryptography` library. It indicates that the data being decrypted has been tampered with, or the wrong encryption key is being used for decryption, or the data itself is corrupted. This can happen if `ENCRYPTED_FIELD_KEYS` changes after data has been written.fixVerify that the `ENCRYPTED_FIELD_KEYS` used to decrypt the data is identical to the key used to encrypt it. Check for accidental key rotation or discrepancies between environments (e.g., development vs. production keys).
Warnings
- breaking Data encrypted with versions prior to 0.6.0 (which used `pycrypto`) is NOT readable by versions 0.6.0 and later (which use `cryptography`). An explicit data migration is required if upgrading from versions < 0.6.0 to >= 0.6.0.
- breaking The `KEY_GENERATOR` and `KEY_STORE` settings were removed in version 0.6.0. They have been replaced by the `ENCRYPTED_FIELD_KEYS` dictionary setting.
- gotcha Failing to define `ENCRYPTED_FIELD_KEYS` in your Django `settings.py` or providing an invalid structure (e.g., not a dictionary or string) will lead to an `ImproperlyConfigured` exception or `MissingKeyError` when attempting to save or retrieve data.
Install
-
pip install django-encrypted-model-fields
Imports
- EncryptedCharField
from encrypted_model_fields.fields import EncryptedCharField
- EncryptedTextField
from encrypted_model_fields.fields import EncryptedTextField
- EncryptedIntegerField
from encrypted_model_fields.fields import EncryptedIntegerField
- EncryptedJSONField
from encrypted_model_fields.fields import EncryptedJSONField
Quickstart
import os
from django.conf import settings
from django.db import models
# Minimal Django settings for standalone script testing
if not settings.configured:
settings.configure(
DEBUG=True,
INSTALLED_APPS=[
'encrypted_model_fields' # Required for migrations/field registration
],
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
},
# CRUCIAL: Define encryption keys
ENCRYPTED_FIELD_KEYS={
'default': os.environ.get('ENCRYPTED_FIELD_DEFAULT_KEY', 'a_secret_key_for_dev_or_testing_ONLY_do_not_use_in_prod'),
'billing': os.environ.get('ENCRYPTED_FIELD_BILLING_KEY', 'another_key_for_billing_data')
},
# Required for any Django project
SECRET_KEY='django-insecure-testkey-very-insecure'
)
# Ensure apps are loaded (needed for standalone scripts)
import django
django.setup()
from encrypted_model_fields.fields import EncryptedCharField, EncryptedTextField
class Customer(models.Model):
name = models.CharField(max_length=100)
email = EncryptedCharField(max_length=255)
notes = EncryptedTextField(blank=True, null=True, key_name='billing') # Use a specific key
def __str__(self):
return self.name
# Example usage (requires database setup, e.g., via manage.py migrate)
if __name__ == '__main__':
# This part would typically be in a Django shell or view
print("--- Quickstart Example ---")
# In a real Django project, you'd run 'python manage.py makemigrations' and 'python manage.py migrate'
# For this standalone script, we'll simulate migrations for Customer model
from django.apps import apps
from django.core.management.commands import makemigrations, migrate
from io import StringIO
# Simulate makemigrations
# Note: This is a hack for a standalone script, not for production use.
# In a real project, 'encrypted_model_fields' would be in INSTALLED_APPS and 'python manage.py makemigrations/migrate' would be run.
# Create a simple fake app for the model
class FakeAppConfig(apps.AppConfig):
name = 'my_app'
verbose_name = 'My App'
apps.apps_ready = False # Reset app loading for standalone execution
apps.clear_cache()
apps.set_available_apps(['my_app'])
apps.populate(settings.INSTALLED_APPS + ['my_app'])
settings.INSTALLED_APPS += ['my_app']
apps.get_app_config('my_app').models = {'customer': Customer}
# For real migration, save models.py in an app and run commands.
# For this quickstart, we'll just interact with the model directly in memory after setup.
# Create a record
customer = Customer.objects.create(name='Alice', email='alice@example.com', notes='Confidential billing info.')
print(f"Created customer: {customer.name}")
print(f"Stored email (encrypted): {customer.email}") # Will show encrypted value if accessed directly before decrypt
# Retrieve and decrypt
retrieved_customer = Customer.objects.get(name='Alice')
print(f"Retrieved email (decrypted): {retrieved_customer.email}")
print(f"Retrieved notes (decrypted): {retrieved_customer.notes}")
# Update
retrieved_customer.email = 'alice.smith@example.com'
retrieved_customer.save()
print(f"Updated email: {Customer.objects.get(name='Alice').email}")