Writing a Custom Model Field for Encryption

Tuesday, July 23, 2019

Lately I’ve been learning web development with Django. I have a few projects I’ve been working on and have learned a ton. Something I came across in one of my projects was the need to encrypt fields in the database. Being a DevOps/Security guy I know the whole arguments against and for using encryption on specific database fields. In this case I am storing some usernames and passwords for a site I am scraping, and felt that encryption was better than nothing. I found an article but didn’t like the exact way they were implementing the encryption so I changed it. Below is that implementation:

class EncryptedText(models.TextField):

    description = "An encrypted text field for storing sensitive information"

    def __init__(self, *args, **kwargs):
        self.__key = os.environ.get('FERNET_KEY', None)
        if self.__key is None:
            raise ValueError('No FERNET_KEY environment variable found!')
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, key, args, kwargs = super().deconstruct()
        return name, key, args, kwargs

    def from_db_value(self, value, expression, connection):
        if self.__key and value:
            value = base64.b64decode(value)
            f = Fernet(self.__key)
            value = f.decrypt(value)
        return value

    def to_python(self, value):
        return value

    def get_db_prep_value(self, value, connection, prepared=False):
        if value:
            if self.__key:
                f = Fernet(self.__key)
                value = f.encrypt(value.encode())
                value = base64.b64encode(value).decode('utf-8')
        return value

The biggest modification I made was changing the way the key was stored. In the article I found they used a file on the filesystem to store the key. Being that I would likely deploy this to some containerization service (Kubernetes, Heroku, etc.) filesystem storage is a no-no as any app rebuild would destroy the filesystem and render my data unreadable. This change instead uses the environment variable FERNET_KEY.

If you want to look at the original article you can here: https://dadruid5.com/2018/08/28/encrypting-data-in-django-with-the-fernet-algorithm/


Quickly deploying Python code to Lambda

drone.io and the GCR