Django Model Field for a Secure String

1.4k Views Asked by At

In short: I want to store strings encrypted in the database and decrypt them when i need them. What is the best practice for this?

Context: I am building an application that uses a 3rd party API that requires a login and password. I want to store the credentials that will be used for the 3rd party API in the database as their will be many different credentials. I was looking on the django documentation and on stackoverflow but i only find things that hash passwords.

EDIT: I did some more searching and i realy liked this: https://github.com/georgemarshall/django-cryptography

Anyone some other ideas or opinions on the repository i found?

2

There are 2 best solutions below

1
On BEST ANSWER

You should create a custom model field and override the from_db_value() and get_prep_value to encrypted and decrypt the string. You can use many packages to do the encryption algorithm.

With cryptography you can do something like:

from django.db.models import CharField
from cryptography.fernet import Fernet
from django.conf import settings
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

class SecureString(CharField):
    """Custom Encrypted Field"""

    kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), 
                     length=32, 
                     salt=salt, 
                     iterations=100000, 
                     backend=default_backend())

    key = base64.urlsafe_b64encode(kdf.derive(settings.SECRET_KEY))

    f = Fernet(key)

    def from_db_value(self, value, expression, connection):
        return f.decrypt(value)

    def get_prep_value(self, value):
        return f.encrypt(value)

Note: You should abstract the encryption logic for a more optimal solution.

0
On

This ended up being how I adapted the script to make it work properly with the previous fields where I had insecure credentials. I had to make some adjustments based on this

class SecureCharField(CharField):
    """
    Safe string field that gets encrypted before being stored in the database, and
    decrypted when retrieved. Since the stored values have a fixed length, using the
    "max_length" parameter in your field will not work, it will be overridden by default.
    """

    def __init__(self, *args: Any, **kwargs: Any):
        kwargs['max_length'] = 512
        super().__init__(*args, **kwargs)

    salt = bytes(os.environ["SECURE_STRING_SALT"], 'utf-8')
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=100000,
        backend=default_backend()
    )

    # Encode the FERNET encryption key
    key = base64.urlsafe_b64encode(kdf.derive(
        bytes(os.environ['FERNET_ENCRYPTION_KEY'], 'utf-8')
    ))

    # Create a "fernet" object using the key stored in the .env file
    f = Fernet(key)

    def from_db_value(self, value: str, expression: Any, connection: Any) -> str:
        """
        Decrypts the value retrieved from the database.
        """
        value = str(self.f.decrypt(bytes(value, 'cp1252')), encoding='utf-8')
        return value

    def get_prep_value(self, value: str) -> str:
        """
        Encrypts the value before storing it in the database.
        """
        value = str(self.f.encrypt(bytes(value, 'utf-8')), 'cp1252')
        return value