1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 | from django.db import models
from django import forms
from django.conf import settings
import binascii
import random
import string
class EncryptedString(str):
"""A subclass of string so it can be told whether a string is
encrypted or not (if the object is an instance of this class
then it must [well, should] be encrypted)."""
pass
class BaseEncryptedField(models.Field):
def __init__(self, *args, **kwargs):
cipher = kwargs.pop('cipher', 'AES')
imp = __import__('Crypto.Cipher', globals(), locals(), [cipher], -1)
self.cipher = getattr(imp, cipher).new(settings.SECRET_KEY[:32])
models.Field.__init__(self, *args, **kwargs)
def to_python(self, value):
#values should always be encrypted no matter what!
#raise an error if tthings may have been tampered with
return self.cipher.decrypt(binascii.a2b_hex(str(value))).split('\0')[0]
def get_db_prep_value(self, value):
if value is not None and not isinstance(value, EncryptedString):
padding = self.cipher.block_size - len(value) % self.cipher.block_size
if padding and padding < self.cipher.block_size:
value += "\0" + ''.join([random.choice(string.printable) for index in range(padding-1)])
value = EncryptedString(binascii.b2a_hex(self.cipher.encrypt(value)))
return value
class EncryptedTextField(BaseEncryptedField):
__metaclass__ = models.SubfieldBase
def get_internal_type(self):
return 'TextField'
def formfield(self, **kwargs):
defaults = {'widget': forms.Textarea}
defaults.update(kwargs)
return super(EncryptedTextField, self).formfield(**defaults)
class EncryptedCharField(BaseEncryptedField):
__metaclass__ = models.SubfieldBase
def get_internal_type(self):
return "CharField"
def formfield(self, **kwargs):
defaults = {'max_length': self.max_length}
defaults.update(kwargs)
return super(EncryptedCharField, self).formfield(**defaults)
|
Comments
I seems, that you need to make real max_length twice bigger, than len(value)+padding, because encrypted data is bigger.
#
Using a credit card number as an example for this is a bad idea. This isn't PCI compliant and storing CC numbers on your server is a great way to get into a lot of trouble (not to mention pissing your customers off). There is almost no reason to store a CC number, gateways like Authorize.net can do that for you and stay completely compliant.
Also no credit card company in the world allows you to keep CVV for any amount of time, so that shouldn't ever be in your model. Even the gateways can't keep this information.
#
I made some changes in this code and have include it to my django app django-fields. Any help and patches are appreciated.
#
Good point jonknee, modified the example as such
#