Django Secured Fields
Django encrypted fields with search enabled.
Requirements
- Python 3.10+
- Django 4.2+
- PostgreSQL, MySQL or SQLite
Features
- Automatically encrypt/decrypt field value using cryptography's Fernet
- Built-in search lookup on the encrypted fields from hashlib's SHA-256 hash value.
inandisnulllookup also supported. - Supports most of available Django fields including
BinaryField,JSONField, andFileField.
Installation
pip install django-secured-fields
Setup
-
Add
secured_fieldsintoINSTALLED_APPS# settings.py INSTALLED_APPS = [ ... 'secured_fields', ]
-
Generate a new key using for encryption
$ python manage.py generate_key KEY: TtY8MAeXuhdKDd1HfGUwim-vQ8H7fXyRQ9J8pTi_-lg= HASH_SALT: 500d492e
-
Put generated key(s) and hash salt in settings
# settings.py SECURED_FIELDS_KEY = 'TtY8MAeXuhdKDd1HfGUwim-vQ8H7fXyRQ9J8pTi_-lg=' # or multiple keys for rotation SECURED_FIELDS_KEY = [ 'TtY8MAeXuhdKDd1HfGUwim-vQ8H7fXyRQ9J8pTi_-lg=', '...', ] # optional SECURED_FILDS_HASH_SALT = '500d492e'
Usage
Simple Usage
# models.py
import secured_fields
phone_number = secured_fields.EncryptedCharField(max_length=10)
Enable Searching
# models.py
import secured_fields
id_card_number = secured_fields.EncryptedCharField(max_length=18, searchable=True)
Supported Fields
EncryptedBinaryFieldEncryptedBooleanFieldEncryptedCharFieldEncryptedDateFieldEncryptedDateTimeFieldEncryptedDecimalFieldEncryptedFileFieldEncryptedImageFieldEncryptedIntegerFieldEncryptedJSONFieldEncryptedTextField
Settings
| Key | Required | Default | Description |
|---|---|---|---|
SECURED_FIELDS_KEY |
Yes | Key(s) for using in encryption/decryption with Fernet. Usually generated from python manage.py generate_key. For rotation keys, use a list of keys instead (see MultiFernet). |
|
SECURED_FIELDS_HASH_SALT |
No | '' |
Salt to append after the field value before hashing. Usually generated from python manage.py generate_key. |
SECURED_FIELDS_FILE_STORAGE |
No | 'secured_fields.storage.EncryptedFileSystemStorage' |
File storage class used for storing encrypted file/image fields. See EncryptedStorageMixin |
APIs
Field Arguments
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
searchable |
bool |
No | False |
Enable search function. exact/in lookups are only available when this is True; on a non-searchable field they raise LookupNotSupported. |
Changing searchable on a field with existing records
Existing records stay readable after changing searchable, but they keep the storage format
of the previous flag until they are re-saved:
False→True: existing records do not have a hashed section yet, soexact/inlookups will not match them.True→False: existing records still carry the old hashed section — a deterministic fingerprint of the value — until they are re-saved.
After changing the flag (and running makemigrations/migrate, since the database index changes),
re-save the affected records to convert them to the new format. Do this in a data migration that runs
after the generated AlterField operation — the historical model must already carry the new
searchable value, otherwise the records are silently re-written in the old format:
def resave_records(apps, schema_editor):
model = apps.get_model('myapp', 'MyModel')
model.objects.bulk_update(model.objects.all(), ['my_field'], batch_size=1000)
bulk_update re-encrypts through the same path as save() but batches the queries, and — unlike
re-saving each record — does not overwrite auto_now date/datetime fields with the migration run time.
Encryption
> from secured_fields.fernet import get_fernet
> data = b'test'
> encrypted_data = get_fernet().encrypt(data)
> encrypted_data
b'gAAAAABh2_Ry_thxLTuFFXeMc9hNttah82979JPuMSjnssRB0DmbgwdtEU5dapBgISOST_a_egDc66EG_ZtVu_EqF_69djJwuA=='
> get_fernet().decrypt(encrypted_data)
b'test'
Rotate Keys
> from secured_fields.fernet import get_fernet
> encrypted_data = get_fernet().encrypt(b'test')
> encrypted_data
b'gAAAAABh2_Ry_thxLTuFFXeMc9hNttah82979JPuMSjnssRB0DmbgwdtEU5dapBgISOST_a_egDc66EG_ZtVu_EqF_69djJwuA=='
> rotated_encrypted_data = get_fernet().rotate(encrypted_data)
> get_fernet().decrypt(rotated_encrypted_data)
b'test'
See more details in MultiFernet.rotate.
EncryptedMixin
If you have a field which is not supported by the package, you can use EncryptedMixin to enable encryption and search functionality for that custom field.
import secured_fields
from django.db import models
class EncryptedUUIDField(secured_fields.EncryptedMixin, models.UUIDField):
pass
task_id = EncryptedUUIDField(searchable=True)
EncryptedStorageMixin
If you use a custom file storage class (e.g. defined in settings.py's STORAGES), you can enable file encryption using EncryptedStorageMixin.
import secured_fields
from minio_storage.storage import MinioMediaStorage
class EncryptedMinioMediaStorage(
secured_fields.EncryptedStorageMixin,
MinioMediaStorage,
):
pass
Known Limitation
inlookup onJSONFieldis not available- Large files are not performance-friendly at the moment (see #2)
- Search on
BinaryFielddoes not supported at the moment (see #6) - Changing
searchableon a field with existing records requires re-saving the records to make search results consistent (see Changingsearchableon a field with existing records) update()only accepts plain values orValue()for encrypted fields; other expressions such asF(),Caseor database functions raiseExpressionNotSupported, since their result would be written unencrypted (see #52).bulk_update()is supported
Development
Requirements
- Docker
- Poetry 2.0+
- MySQL Client
brew install mysql-clientecho 'export PATH="/usr/local/opt/mysql-client/bin:$PATH"' >> ~/.bash_profile
Running Project
-
Start backend databases
make up-db -
Run tests (see: Testing)
Linting
make lint
Testing
make test-pg # or make test-mysql, make test-sqlite
Fix Formatting
make yapf
Metadata
Release files for django-secured-fields 0.5.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_secured_fields-0.5.2.tar.gz | 12.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_secured_fields-0.5.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.7 kB
Release files / django_secured_fields-0.5.2.tar.gz
| Download URL | django_secured_fields-0.5.2.tar.gz |
|---|---|
| Size | 12.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f806f5aced5d3c8a6533801a20a64bc64f7d1b0c143e798fddc17a604d9ca4d
|
|
BLAKE2b-256 checksum How to use checksums |
eef059563a3ddad978fe305fc79362a59359371933a0532e631631753a16f6b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.3.3 CPython/3.13.15 Linux/6.17.0-1022-azure
|
Release files / django_secured_fields-0.5.2-py3-none-any.whl
| Download URL | django_secured_fields-0.5.2-py3-none-any.whl |
|---|---|
| Size | 13.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e9b1f4b6b69bf8b9a7816e49a9d14988794ee77a1e0daccd481920822ab9d9c0
|
|
BLAKE2b-256 checksum How to use checksums |
a7c67aeca5da949cc01af1c274e7cc9bdf0197887bf94ef78af912a12f568d85
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.3.3 CPython/3.13.15 Linux/6.17.0-1022-azure
|