{"id":8097,"library":"django-fernet-encrypted-fields","title":"Django Fernet Encrypted Fields","description":"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.","status":"active","version":"0.4.0","language":"en","source_language":"en","source_url":"https://github.com/jazzband/django-fernet-encrypted-fields/","tags":["Django","encryption","security","fields","Fernet","cryptography","data-protection"],"install":[{"cmd":"pip install django-fernet-encrypted-fields","lang":"bash","label":"Install via pip"}],"dependencies":[{"reason":"Core framework dependency for model fields.","package":"Django"},{"reason":"Provides the underlying Fernet symmetric encryption primitive.","package":"cryptography","optional":false}],"imports":[{"symbol":"EncryptedCharField","correct":"from encrypted_fields.fields import EncryptedCharField"},{"symbol":"EncryptedTextField","correct":"from encrypted_fields.fields import EncryptedTextField"},{"symbol":"EncryptedIntegerField","correct":"from encrypted_fields.fields import EncryptedIntegerField"},{"symbol":"EncryptedEmailField","correct":"from encrypted_fields.fields import EncryptedEmailField"},{"note":"While functional, wildcard imports are generally discouraged for clarity and to prevent namespace pollution.","wrong":"from encrypted_fields.fields import *","symbol":"* (wildcard import)","correct":"from encrypted_fields.fields import EncryptedTextField"},{"note":"This import path belongs to an older, different package (`django-fernet-fields`) and will not work with `django-fernet-encrypted-fields`.","wrong":"from fernet_fields import EncryptedTextField","symbol":"EncryptedTextField (from old package)","correct":"from encrypted_fields.fields import EncryptedTextField"}],"quickstart":{"code":"import os\nfrom django.db import models\nfrom encrypted_fields.fields import EncryptedTextField\n\n# In your Django settings.py file, define SALT_KEY\n# For production, load from environment variables and ensure it's a strong, random string.\n# Example for settings.py (not for production directly):\n# import os\n# os.environ.setdefault('DJANGO_SALT_KEY', '0123456789abcdefghijklmnopqrstuvwxyz')\nSALT_KEY = os.environ.get('DJANGO_SALT_KEY', 'a_default_32_char_salt_key_for_dev')\n# For Django >= 4.1, you can also use SECRET_KEY_FALLBACKS for SECRET_KEY rotation.\n# SECRET_KEY_FALLBACKS = [os.environ.get('OLD_DJANGO_SECRET_KEY', '')]\n\nclass MyEncryptedModel(models.Model):\n    sensitive_data = EncryptedTextField()\n    name = models.CharField(max_length=255)\n\n    def __str__(self):\n        return self.name\n\n# Example usage (assuming Django setup and migrations are run):\n# from myapp.models import MyEncryptedModel\n# instance = MyEncryptedModel.objects.create(name='Test User', sensitive_data='This is a secret message.')\n# print(instance.sensitive_data) # Automatically decrypted: 'This is a secret message.'\n# print(instance.pk)\n","lang":"python","description":"To get started, install the library and configure your `settings.py` with a `SALT_KEY` (or rely on `SECRET_KEY` as a fallback, which is less secure for specific field encryption). Define your model with an `EncryptedTextField` or other provided encrypted field types. Data will be automatically encrypted and decrypted during save and retrieve operations. Remember to run `makemigrations` and `migrate`."},"warnings":[{"fix":"Follow the three-step data migration process: 1. Add new encrypted field with a different name and `null=True`. 2. Create a data migration to copy values from old to new field. 3. Remove the old field and rename the new field if desired.","message":"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.","severity":"breaking","affected_versions":"All versions"},{"fix":"Avoid using `db_index=True`, `unique=True`, or `primary_key=True` on `EncryptedField` instances. Design your schema such that lookups and indexing are performed on unencrypted, non-sensitive fields.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Store `SALT_KEY` in a secure environment variable or secrets management system. Implement robust backup procedures for your keys. For key rotation, use `SALT_KEY` as a list of keys, or `SECRET_KEY_FALLBACKS` (Django >= 4.1) for `SECRET_KEY` rotation.","message":"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.","severity":"breaking","affected_versions":"All versions"},{"fix":"For sensitive fields, consider making them non-nullable and storing a 'sentinel' empty value (which will be encrypted) instead of `None`.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure `cryptography` is explicitly listed in `requirements.txt` with a version pin. Run `pip freeze > requirements.txt` after installing all dependencies in a clean virtual environment.","message":"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.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Verify 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).","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.","error":"InvalidToken / Cannot decrypt data"},{"fix":"Remove `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.","cause":"An `EncryptedField` (or its subclasses) has `db_index=True`, `unique=True`, or `primary_key=True` set.","error":"django.core.exceptions.ImproperlyConfigured: EncryptedField cannot be indexed."},{"fix":"Ensure 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`.","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.","error":"incorrect padding"},{"fix":"Increase 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.","cause":"Encrypted data is typically longer than the original plaintext. The `max_length` of the underlying `CharField` might be insufficient.","error":"Data too long for field (e.g., 'value too long for type character varying(X)')"}]}