django-fernet-encrypted-fields/encrypted_fields/fields.py

90 lines
2.6 KiB
Python
Raw Normal View History

2021-09-30 14:27:19 +00:00
import base64
from django.conf import settings
from cryptography.fernet import Fernet, MultiFernet
2021-09-30 14:27:19 +00:00
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from django.db import models
from django.utils.functional import cached_property
2021-09-30 14:27:19 +00:00
class EncryptedFieldMixin(object):
@cached_property
def keys(self):
keys = []
salt_keys = settings.SALT_KEY if isinstance(settings.SALT_KEY, list) else [settings.SALT_KEY]
for salt_key in salt_keys:
salt = bytes(salt_key, 'utf-8')
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=default_backend())
keys.append(base64.urlsafe_b64encode(kdf.derive(settings.SECRET_KEY.encode('utf-8'))))
return keys
2021-09-30 14:27:19 +00:00
@cached_property
def f(self):
if len(self.keys) == 1:
return Fernet(self.keys[0])
return MultiFernet([Fernet(k) for k in self.keys])
2021-09-30 14:27:19 +00:00
def get_internal_type(self):
"""
To treat everything as text
"""
return 'TextField'
def get_prep_value(self, value):
if value:
if not isinstance(value, str):
value = str(value)
return self.f.encrypt(bytes(value, 'utf-8')).decode('utf-8')
return None
def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
return value
def from_db_value(self, value, expression, connection):
return self.to_python(value)
def to_python(self, value):
if value is None or not isinstance(value, str):
return value
value = self.f.decrypt(bytes(value, 'utf-8')).decode('utf-8')
return super(EncryptedFieldMixin, self).to_python(value)
class EncryptedCharField(EncryptedFieldMixin, models.CharField):
pass
class EncryptedTextField(EncryptedFieldMixin, models.TextField):
pass
class EncryptedDateTimeField(EncryptedFieldMixin, models.DateTimeField):
pass
class EncryptedIntegerField(EncryptedFieldMixin, models.IntegerField):
pass
class EncryptedDateField(EncryptedFieldMixin, models.DateField):
pass
class EncryptedFloatField(EncryptedFieldMixin, models.FloatField):
pass
class EncryptedEmailField(EncryptedFieldMixin, models.EmailField):
pass
class EncryptedBooleanField(EncryptedFieldMixin, models.BooleanField):
pass