Skip to content

Commit

Permalink
tests to pass, add additional tests for field cryptor
Browse files Browse the repository at this point in the history
  • Loading branch information
erikvw committed Jun 6, 2016
1 parent 389cb3b commit 246bf4d
Show file tree
Hide file tree
Showing 25 changed files with 748 additions and 565 deletions.
21 changes: 16 additions & 5 deletions django_crypto_fields/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,29 @@ class CryptoFieldsAdminSite(AdminSite):
"""
For example:
add to urls:
url(r'^call_manager/', call_manager_admin.urls),
url(r'^admin/', encryption_admin.urls),
then:
>>> reverse('call_manager_admin:edc_call_manager_call_add')
'/call_manager/edc_call_manager/call/add/'
>>> reverse('encryption_admin:django_crypto_fields_crypt_add')
'/admin/django_crypto_fields/crypt/add/'
"""
site_header = 'Data Encryption Administration'
site_title = 'Data Encryption Administration'
index_title = 'Data Encryption'
site_url = '/crypto_fields/'
encryption_admin = CryptoFieldsAdminSite(name='encryption_admin')
crypto_fields_admin = CryptoFieldsAdminSite(name='encryption_admin')


@admin.register(Crypt, site=encryption_admin)
@admin.register(Crypt, site=crypto_fields_admin)
class CryptAdmin(admin.ModelAdmin):

date_hierarchy = 'modified'

fields = sorted([field.name for field in Crypt._meta.fields])

readonly_fields = [field.name for field in Crypt._meta.fields]

list_display = ('algorithm', 'hash', 'modified', 'hostname_modified')

list_filter = ('algorithm', 'modified', 'hostname_modified')

search_fields = ('hash', )
32 changes: 30 additions & 2 deletions django_crypto_fields/apps.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
import sys

from django.apps import AppConfig
from django_crypto_fields.classes.keys import Keys

from django.core.management.color import color_style


class DjangoCryptoFieldsError(Exception):
pass


class DjangoCryptoFieldsConfig(AppConfig):
name = 'django_crypto_fields'
verbose_name = "Data Encryption"
encryption_keys = Keys()
encryption_keys = None

def __init__(self, app_label, model_name):
"""Placed here instead of `ready()`. For models to load correctly that use
field classes from this module the keys need to be loaded before models."""
super(DjangoCryptoFieldsConfig, self).__init__(app_label, model_name)
from django_crypto_fields.keys import Keys
keys = Keys()
if not self.encryption_keys:
style = color_style()
sys.stdout.write('Loading {} ...\n'.format(self.verbose_name))
if not keys.key_files_exist():
sys.stdout.write(style.NOTICE('Warning: {} failed to load encryption keys.\n'.format(
self.verbose_name)))
sys.stdout.write('Confirm that settings.KEY_PATH points to the correct folder.\n')
sys.stdout.write('Loading the wrong encryption keys can corrupt sensitive data.\n')
sys.stdout.write('If this is your first time loading the project, '
'new keys will be generated\n')
sys.stdout.write('and placed in the settings.KEY_PATH folder.\n')
keys.create_keys(self.keys.key_path, self.keys.key_prefix)
keys.load_keys()
self.encryption_keys = keys
4 changes: 0 additions & 4 deletions django_crypto_fields/classes/__init__.py

This file was deleted.

80 changes: 0 additions & 80 deletions django_crypto_fields/classes/cryptor.py

This file was deleted.

85 changes: 0 additions & 85 deletions django_crypto_fields/classes/keys.py

This file was deleted.

39 changes: 3 additions & 36 deletions django_crypto_fields/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os
from django.core.management.color import color_style

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
style = color_style()

AES = 'aes'
CIPHER_BUFFER_SIZE = 10
Expand All @@ -16,36 +15,4 @@
RSA = 'rsa'
RSA_KEY_SIZE = 2048
SALT = 'salt'

try:
KEY_PREFIX = settings.KEY_PREFIX
except (ImproperlyConfigured, AttributeError) as e:
KEY_PREFIX = 'user'

try:
KEY_PATH = settings.KEY_PATH
except (ImproperlyConfigured, AttributeError) as e:
KEY_PATH = settings.BASE_DIR
KEY_PREFIX = 'test'
print('Warning! Not ready for production. {}. Setting KEY_PATH to {} for testing purposes.'.format(e, KEY_PATH))

KEY_FILENAMES = {
# algorithm : {mode: {key:path}}
'rsa': {
'restricted': {
'public': os.path.join(KEY_PATH, KEY_PREFIX + '-rsa-restricted-public.pem'),
'private': os.path.join(KEY_PATH, KEY_PREFIX + '-rsa-restricted-private.pem')},
'local': {
'public': os.path.join(KEY_PATH, KEY_PREFIX + '-rsa-local-public.pem'),
'private': os.path.join(KEY_PATH, KEY_PREFIX + '-rsa-local-private.pem')}},
'aes': {
'local': {
'private': os.path.join(KEY_PATH, KEY_PREFIX + '-aes-local.key')},
'restricted': {
'private': os.path.join(KEY_PATH, KEY_PREFIX + '-aes-restricted.key')}},
'salt': {
'local': {
'private': os.path.join(KEY_PATH, KEY_PREFIX + '-salt-local.key')},
'restricted': {
'private': os.path.join(KEY_PATH, KEY_PREFIX + '-salt-restricted.key')}},
}
RESTRICTED_MODE = 'restricted'
91 changes: 91 additions & 0 deletions django_crypto_fields/cryptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import sys

from Crypto import Random
from Crypto.Cipher import AES as AES_CIPHER

from django.apps import apps as django_apps
from django.core.exceptions import AppRegistryNotReady

from .constants import RSA, AES, PRIVATE, PUBLIC, ENCODING, style
from .exceptions import EncryptionError


class Cryptor(object):
"""Base class for all classes providing RSA and AES encryption methods.
The PEM file names and paths are in KEY_FILENAMES. KEYS is a copy of this except the
filenames are replaced with the actual keys."""

def __init__(self, keys=None):
try:
# ignore "keys" parameter if Django is loaded
self.keys = django_apps.get_app_config('django_crypto_fields').encryption_keys
except AppRegistryNotReady:
self.keys = keys

def aes_encrypt(self, plaintext, mode):
try:
plaintext = plaintext.encode(ENCODING)
except AttributeError:
pass
attr = '_'.join([AES, mode, PRIVATE, 'key'])
aes_key = getattr(self.keys, attr)
iv = Random.new().read(AES_CIPHER.block_size)
cipher = AES_CIPHER.new(aes_key, AES_CIPHER.MODE_CFB, iv)
return iv + cipher.encrypt(plaintext)

def aes_decrypt(self, ciphertext, mode):
attr = '_'.join([AES, mode, PRIVATE, 'key'])
aes_key = getattr(self.keys, attr)
iv = ciphertext[:AES_CIPHER.block_size]
cipher = AES_CIPHER.new(aes_key, AES_CIPHER.MODE_CFB, iv)
plaintext = cipher.decrypt(ciphertext)[AES_CIPHER.block_size:]
return plaintext.decode(ENCODING)

def rsa_encrypt(self, plaintext, mode):
attr = '_'.join([RSA, mode, PUBLIC, 'key'])
rsa_key = getattr(self.keys, attr)
try:
plaintext = plaintext.encode(ENCODING)
except AttributeError:
pass
try:
ciphertext = rsa_key.encrypt(plaintext)
except (ValueError, TypeError) as e:
raise EncryptionError('RSA encryption failed for value. Got \'{}\''.format(e))
return ciphertext

def rsa_decrypt(self, ciphertext, mode):
attr = '_'.join([RSA, mode, PRIVATE, 'key'])
rsa_key = getattr(self.keys, attr)
plaintext = rsa_key.decrypt(ciphertext)
return plaintext.decode(ENCODING)

def test_rsa(self):
""" Tests keys roundtrip"""
plaintext = 'erik is a pleeb! ERIK IS A PLEEB 0123456789!@#$%^&*()_-+={[}]|\"\':;>.<,?/~`±§'
for mode in self.keys.key_filenames.get(RSA):
try:
ciphertext = self.rsa_encrypt(plaintext, mode)
sys.stdout.write(style.SUCCESS('(*) Passed encrypt: {}\n'.format(
self.keys.key_filenames[RSA][mode][PUBLIC])))
except (AttributeError, TypeError) as e:
sys.stdout.write(style.ERROR('( ) Failed encrypt: {} public ({})\n'.format(mode, e)))
try:
assert plaintext == self.rsa_decrypt(ciphertext, mode)
sys.stdout.write(style.SUCCESS('(*) Passed decrypt: {}\n'.format(
self.keys.key_filenames[RSA][mode][PRIVATE])))
except (AttributeError, TypeError) as e:
sys.stdout.write(style.ERROR('( ) Failed decrypt: {} private ({})\n'.format(mode, e)))

def test_aes(self):
""" Tests keys roundtrip"""
plaintext = 'erik is a pleeb!\nERIK IS A PLEEB\n0123456789!@#$%^&*()_-+={[}]|\"\':;>.<,?/~`±§\n'
for mode in self.keys.key_filenames[AES]:
ciphertext = self.aes_encrypt(plaintext, mode)
assert plaintext != ciphertext
sys.stdout.write(style.SUCCESS('(*) Passed encrypt: {}\n'.format(
self.keys.key_filenames[AES][mode][PRIVATE])))
assert plaintext == self.aes_decrypt(ciphertext, mode)
sys.stdout.write(style.SUCCESS('(*) Passed decrypt: {}\n'.format(
self.keys.key_filenames[AES][mode][PRIVATE])))
6 changes: 6 additions & 0 deletions django_crypto_fields/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
class DjangoCryptoFieldsLoadingError(Exception):
pass


class DjangoCryptoFieldsKeysAlreadyLoaded(Exception):
pass


class EncryptionError(Exception):
Expand Down

0 comments on commit 246bf4d

Please sign in to comment.