Encrypted Model Fields

If your custom_code or reusable app contains a Model field storing user-specific sensitive data, then you may want to encrypt that data.

Examples of user-specific sensitive data include a password or API key for an external service that your TOM uses. For example, TOMToolkit Facility modules can use the mechanism described here to store, encrypted, user-specific credentials in a user profile model. Examples include the tom_eso and the tom_swift facility modules.

As we explain below, TOMToolkit provides a mix-in class, a property descriptor, and utility functions to help encrypt user-specific sensitive data and access it when it’s needed.

Note

For sensitive data that is used by the TOM itself and is not user-specific, we suggest that this data be stored outside the TOM and accessed through environment variables.

Creating and accessing an encrypted Model field

Creating an encrypted Model field

If your Model has a field that should be encrypted, follow these steps:

  1. Import the mix-in class and property descriptor in your models.py:

from tom_common.models import EncryptableModelMixin, EncryptedProperty
  1. Make your Model subclass a subclass of EcryptableModelMixin. For example:

class MyAppModel(EncryptableModelMixin, models.Model):
    ...

This gives your model access to a set of methods that will manage the encryption and decryption of your data into and out of the BinaryField that stores the encrypted data.

3. Add the BinaryField that will store the encrypted data and the property descriptor through which the BinaryField will be accessed.

_ciphertext_api_key = BinaryField(null=True, blank=True)  # encrypted data field (private)
api_key = EncryptedProperty('_ciphertext_api_key')  # descriptor that provides access (public)

By convention name of the BinaryField field should begin with and underscore (_ciphertext_api_key in our example) because is it private to the Model class.

Accessing encrypted data

The following example shows how to get and set an encrypted field using the utility methods provided in tom_common.session_utils.py:

from tom_common.session_utils import get_encrypted_field, set_encrypted_field
from tom_app_example.models import MyAppModel

profile: MyAppModel = user.myappmodel  # Model instance containing an encrypted field

# getter example
decrypted_api_key: str = get_encrypted_field(user, profile, 'api_key')

# setter example
new_api_key: str = 'something_secret'
set_encrypted_field(user, profile, 'api_key', new_api_key)

Note here that the User instance (user) is used to access the EncryptableModelMixin subclass and its encrypted data. The user property of the Model subclass containing the encrypted field (MyAppModel in our example) is provided by the EncryptableModelMixin. As such, the model should not define a user property of its own.

Some Explanations

EncryptableModelMixin (source)

The User’s data is encrypted using (among other things) their password (i.e the password they use to login to your TOM). When the User changes their password, their encrypted data re-encrypted accordingly. The EncryptableModelMixin adds method for this to your otherwise normal Django model.

EncryptedProperty (source)

A property descriptor implements the Python descriptor protocol (__get__, __set__, etc). The EncryptedProperty property descriptor handles the details of decrypting the encrypted BinaryField on its way out of the database and encrypting it on the way in. It is invoked when the property is accessed (e.g. model_instance.api_key).

Session Utils (example)

The get_encrypted_field and set_encrypted_field functions implement boilerplate code for creating and destroying the cipher used to encrypt and decrypt the BinaryField. These methods must always be used to access any encrypted field.

The rest of the details are in the source code. If reading source code isn’t your thing, please do feel free to get in touch and we’ll be happy to answer any questions you may have.