Python Django: Create a more secure database by using a custom field
Tags: django, python, database, security
Published: 2023-01-17 17:19:05

One way of enhancing the security of a database is by encrypting it. Luckily by utilizing Django, we can do this automatically. We only need the signing and models packages from the core Django packages.

Let's start by importing the required packages:

```
from django.core import signing
from django.db.models import TextField
```

First, the `signing` is a cryptographic signing package; it allows encrypting and decrypting using the `SECRET_KEY` found in `settings.py`; more info can be found at the bottom, and we need the `TextField` because we will extend the default one with more functionality.

---

Now let's create and init the new field. I'll name it `EncryptedTextField`:

```
class EncryptedTextField(models.TextField):
    def __init__(self, *args, **kwargs):
        super(EncryptedTextField, self).__init__(*args, **kwargs)
```

---

Now for the encrypting part:

```
    def get_db_prep_save(self, value, connection):
        value = super().get_db_prep_value(value, connection)
        if value is None:
            return value

        if len(value) == 0:
            return value

        return ((signing.dumps(str(value))).encode('utf-8')).decode('utf-8')
```

The `get_db_prep_save` is a function that runs when we try to save data to the database; we are getting the raw data by `get_db_prep_value` and then return an encrypted version of it `return ((signing.dumps(str(value))).encode('utf-8')).decode('utf-8')`. More info can be found at the bottom.

---

Now for the decrypting part:

```
    def from_db_value(self, value, *args, **kwargs):
        if value is None:
            return value

        if len(value) == 0:
            return value

        return signing.loads(value)
```

The `def from_db_value` is a function that runs when we try to get data from the database, `value` here represents the encrypted data, so we need to decrypt it, which is done here `return signing.loads(value)`. More info can be found at the bottom.
 

---

Example of usage:

```
public_api = EncryptedTextField()
```


---

Example of a full code:

```
from django.db.models import TextField
from django.core import signing

class EncryptedTextField(models.TextField):
    def __init__(self, *args, **kwargs):
        super(EncryptedTextField, self).__init__(*args, **kwargs)

    def get_db_prep_save(self, value, connection):
        value = super().get_db_prep_value(value, connection)
        if value is None:
            return value

        if len(value) == 0:
            return value

        return ((signing.dumps(str(value))).encode('utf-8')).decode('utf-8')

    def to_python(self, value):
        return value

    def from_db_value(self, value, *args, **kwargs):
        if value is None:
            return value

        if len(value) == 0:
            return value
        return signing.loads(value)
```


---
Links to more info:

- The `signing` package: https://docs.djangoproject.com/en/4.1/topics/signing/

- The `get_db_prep_save` function:
https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.Field.get_db_prep_save

- The `from_db_value` function:
https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.Field.from_db_value