User Tools

Site Tools


python:custom_encrypted_field

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

python:custom_encrypted_field [2014/08/17 10:16] (current)
admin created
Line 1: Line 1:
 +====== Custom Encrypted Model Field ======
 +17.08.2014
 +
 +
 +===== Pre requisites =====
 +
 +This code was written for Django 1.6 and Python 2.7. \\ 
 +It's using AES as encryption in CBC mode.
 +
 +
 +===== The goal =====
 +
 +We need to create a custom Model field to be used to encrypt different information (as passwords, sensitive information,​ etc). We mention that we don't need only a hashing function, but more like two ways encryption: we need to be capable of encrypting, but in the same time decrypting information we put into the database.
 +
 +
 +The encryption should deal with UTF-8; it builds the encrypted string as: \\ 
 +
 +**<​prefix><​IV_used_to_encrypt><​encrypted_data>​**
 +
 +
 +The default prefix is '​_',​ but you can specify it in the init method. This prefix is used to make a difference between encrypted and non-encrypted data, so make sure you choose an unique one (related to your encryption method).
 +
 +
 +===== The code =====
 +
 +This code could go in a custom //​fields.py//​ in your project.
 +
 +
 +The important mention here is that you store your secret key in //​settings.py//,​ under the name FIELD_ENCRYPTION_KEY.
 +
 +<code python>
 +# -*- coding: utf-8 -*-
 +
 +import types
 +import base64
 +
 +from Crypto import Random
 +from Crypto.Cipher import AES
 +
 +from django.db import models
 +from yourapp.settings import FIELD_ENCRYPTION_KEY
 +
 +
 +class EncryptedField(models.Field):​
 +    __metaclass__ = models.SubfieldBase
 +
 +    def __init__(self,​ *args, **kwargs):
 +        self.prefix = kwargs.pop('​prefix',​ '​_'​)
 +        super(EncryptedField,​ self).__init__(*args,​ **kwargs)
 +
 +    def get_internal_type(self):​
 +        return '​TextField'​
 +
 +    def to_python(self,​ value):
 +        if value is None or not isinstance(value,​ types.StringTypes):​
 +            return value
 +
 +        if self.is_encrypted(value):​
 +            value = value[len(self.prefix):​] ​   # cut prefix
 +            value = base64.b64decode(value)
 +
 +            iv, encrypted = value[:​AES.block_size],​ value[AES.block_size:​] ​ # extract iv
 +
 +            crypto = AES.new(FIELD_ENCRYPTION_KEY[:​32],​ AES.MODE_CBC,​ iv)
 +            raw_decrypted = crypto.decrypt(encrypted)
 +            value = raw_decrypted.rstrip("​\0"​).decode('​unicode_escape'​)
 +
 +        return value
 +
 +    def get_db_prep_value(self,​ value, connection, prepared=False):​
 +        if not prepared:
 +            iv = Random.new().read(AES.block_size)
 +            crypto = AES.new(FIELD_ENCRYPTION_KEY[:​32],​ AES.MODE_CBC,​ iv)
 +
 +            if isinstance(value,​ types.StringTypes):​
 +                value = value.encode('​unicode_escape'​)
 +                value = value.encode('​ascii'​)
 +            else:
 +                value = str(value)
 +
 +            tag_value = (value + (AES.block_size - len(value) % AES.block_size) * "​\0"​)
 +            value = self.prefix + base64.b64encode(iv + crypto.encrypt(tag_value))
 +
 +        return value
 +
 +    def is_encrypted(self,​ value):
 +        """​checks if a string is encrypted against a static predefined prefix"""​
 +        if self.prefix and value.startswith(self.prefix):​
 +            return True
 +        else:
 +            return False
 +</​code>​
 +
 +
 +===== How it's used =====
 +
 +In your model, assuming that you have a module //​utils/​models//​
 +
 +<code python>
 +from utils.models.fields import EncryptedField
 +...
 +class Example(models.Model):​
 +    name = models.CharField(max_length=128,​ db_index=True,​ unique=True)
 +    password = EncryptedField()
 +</​code>​
 +
  
python/custom_encrypted_field.txt ยท Last modified: 2014/08/17 10:16 by admin