Skip to content

django4-upgrade #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fernet_fields/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .fields import * # noqa

__version__ = '0.5'
__version__ = '0.5.1.4'
42 changes: 35 additions & 7 deletions fernet_fields/fields.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from cryptography.fernet import Fernet, MultiFernet
import base64
import os

from cryptography.fernet import Fernet as OldFernet, MultiFernet
from cryptography.hazmat.backends import default_backend
from django.conf import settings
from django.core.exceptions import FieldError, ImproperlyConfigured
from django.db import models
from django.utils.encoding import force_bytes, force_text
from django.utils.encoding import force_bytes, force_str
from django.utils.functional import cached_property

from . import hkdf
Expand All @@ -16,9 +20,31 @@
'EncryptedIntegerField',
'EncryptedDateField',
'EncryptedDateTimeField',
'StrongerFernet',
]


class StrongerFernet(OldFernet):
# noinspection PyMissingConstructor
def __init__(self, key, backend=None):
if backend is None:
backend = default_backend()

key = base64.urlsafe_b64decode(key)
if len(key) != 48:
raise ValueError(
"Fernet key must be 48 url-safe base64-encoded bytes."
)

self._signing_key = key[:16]
self._encryption_key = key[16:]
self._backend = backend

@classmethod
def generate_key(cls):
return base64.urlsafe_b64encode(os.urandom(48))


class EncryptedField(models.Field):
"""A field that encrypts values using Fernet symmetric encryption."""
_internal_type = 'BinaryField'
Expand All @@ -39,6 +65,7 @@ def __init__(self, *args, **kwargs):
"%s does not support db_index=True."
% self.__class__.__name__
)
self.allowed_unencrypted_values = kwargs.pop('allowed_unencrypted_values', [])
super(EncryptedField, self).__init__(*args, **kwargs)

@cached_property
Expand All @@ -57,8 +84,8 @@ def fernet_keys(self):
@cached_property
def fernet(self):
if len(self.fernet_keys) == 1:
return Fernet(self.fernet_keys[0])
return MultiFernet([Fernet(k) for k in self.fernet_keys])
return StrongerFernet(self.fernet_keys[0])
return MultiFernet([StrongerFernet(k) for k in self.fernet_keys])

def get_internal_type(self):
return self._internal_type
Expand All @@ -71,10 +98,11 @@ def get_db_prep_save(self, value, connection):
retval = self.fernet.encrypt(force_bytes(value))
return connection.Database.Binary(retval)

def from_db_value(self, value, expression, connection, context):
def from_db_value(self, value, expression, connection, context=None):
if value is not None:
value = bytes(value)
return self.to_python(force_text(self.fernet.decrypt(value)))
if value not in self.allowed_unencrypted_values:
value = self.fernet.decrypt(force_bytes(value))
return self.to_python(force_str(value))

@cached_property
def validators(self):
Expand Down
2 changes: 1 addition & 1 deletion fernet_fields/hkdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def derive_fernet_key(input_key):
"""Derive a 32-bit b64-encoded Fernet key from arbitrary input key."""
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=32,
length=48,
salt=salt,
info=info,
backend=backend,
Expand Down
9 changes: 4 additions & 5 deletions fernet_fields/test/test_fields.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from cryptography.fernet import Fernet
from datetime import date, datetime

from django.core.exceptions import FieldError, ImproperlyConfigured
Expand Down Expand Up @@ -29,19 +28,19 @@ def test_key_rotation(self, settings):
settings.FERNET_KEYS = ['key1', 'key2']
f = fields.EncryptedTextField()

enc1 = Fernet(f.fernet_keys[0]).encrypt(b'enc1')
enc2 = Fernet(f.fernet_keys[1]).encrypt(b'enc2')
enc1 = fields.StrongerFernet(f.fernet_keys[0]).encrypt(b'enc1')
enc2 = fields.StrongerFernet(f.fernet_keys[1]).encrypt(b'enc2')

assert f.fernet.decrypt(enc1) == b'enc1'
assert f.fernet.decrypt(enc2) == b'enc2'

def test_no_hkdf(self, settings):
"""Can set FERNET_USE_HKDF=False to avoid HKDF."""
settings.FERNET_USE_HKDF = False
k1 = Fernet.generate_key()
k1 = fields.StrongerFernet.generate_key()
settings.FERNET_KEYS = [k1]
f = fields.EncryptedTextField()
fernet = Fernet(k1)
fernet = fields.StrongerFernet(k1)

assert fernet.decrypt(f.fernet.encrypt(b'foo')) == b'foo'

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def get_version():


setup(
name='django-fernet-fields',
name='singular-django-fernet-fields',
version=get_version(),
description="Fernet-encrypted model fields for Django",
long_description=long_description,
Expand Down