Encrypted attributes

Totara handles a number of ‘secrets’, such as passwords, client IDs/secrets, keys, device access tokens and more, which are used for functions such as authenticating or communicating with external services.

Encrypted attributes allow an attribute of a model to be stored in an encrypted format.

Encrypted attributes are only intended to be used for ephemeral tokens that can be easily regenerated or imported from elsewhere. Do not store personally identifiable or other sensitive information within these attributes.

Encrypted attributes require the OpenSSL module to be installed. When adding encrypted entity attributes, a check must be performed to check if OpenSSL is available. This check is not automatic.

Each encrypted value is tied to the model and instance that encrypted it. Encrypted values cannot be transferred between records or instances. For example, copying an encrypted record from one user to another in the same model will not work.

Defining an encrypted attribute

To make use of encryption, your model must meet the following requirements:

  • The model must extend the abstract class \core\orm\entity\encrypted_model instead of model

  • The model must be in the namespace \{COMPONENT}\model\{MODEL_NAME}

  • The model must populate the attribute $encrypted_attribute_list with the attributes that are being encrypted

  • Your encrypted attribute database column should be a type of text with no maximum length

class my_model extends \core\orm\entiy\encrypted_model { // Remove the attribute from the model whitelist (if it exists) protected $model_accessor_whitelist = [ 'some_field', ]; // Add the attribute to the encrypted whitelist protected $encrypted_attribute_list = [ 'my_token', ]; // Add the attribute to the entity whitelist protected $entity_attribute_whitelist = [ 'my_token', ]; }

To read, accessing the property directly is enough. If the value could not be encrypted, it will instead return false.

// To save, call the set_encrypted_attribute function after the model is created $model->set_encrypted_attribute('my_token', 'plaintext value'); // To read, call the attribute property as normal $clear_value = $model->my_token; // returns 'plaintext value'

A model must be saved first (and have an ID assigned) before setting any encrypted attributes. Encrypted attributes must be set directly through $model->set_encrypted_attribute and only once the model has an ID assigned.

When a new key is added and the encrypted record is updated with the new key, this will be done without calling the normal model save function. Timestamps will not be updated.

Migrating existing records

If you are encrypting an existing column, existing records will need to be handled. Existing records that are stored in plain text will still be returned safely in plain text, and will be encrypted the next time they’re saved via set_encrypted_attribute.

You can queue an ad hoc task to encrypt your new model fully by including the following in your upgrade steps.

$model_class = \my_component\model\my_model::class; \core\task\update_encrypted_models_task::enqueue_one($model_class);

When cron is next run the existing records will all be encrypted.

Encryption keys

The encryption keys are unique to each model, and are created from the master key held in the site.

Keys are stored in dataroot/encryption_keys.json. On first upgrade or install a default key will be generated. New keys can be subsequently added.

Keys are made up of a $timestamp, a $cipher_id and a $key.

Field

Description

Field

Description

$timestamp

The timestamp of when the key was created. The most recent key is what will be used for encryption.

$cipher_id

Reference to the specific cipher class that handles the encryption.

$key

The raw key value used to create the keys.

Encrypted values will always be decrypted using the key they were encrypted with, but will encrypt using the most recent key.

The actual key used to encrypt the data depends on the specific cipher instance used, as each can use the key how they need to. It is intentionally not possible to copy encrypted data from one model and decrypt it in another; each encrypted value is tied to the model and instance that created it.

Ciphers

Ciphers are the classes that handle the actual encryption and decryption. Out of the box Totara includes the default cipher aes256, but custom ciphers can be created as needed.

Creating a custom cipher

Create a new class that implements the interface \core\cipher\contract and implements the following methods:

Method

Returns

Description

Method

Returns

Description

accepts

Array

Must return an array of cipher IDs that represents this specific instance.

Must be unique. Create a unique reference to your class.

make

Instance of cipher

Create a new instance of the cipher class.

encrypt

Encrypted string

Encrypt the provided value with the entity and class provided.

decrypt

Plain-text string

Decrypt the provided value with the entity and class provided.

The actual encrypt and decrypt actions are handled internally.

Afterwards, add a new key with the ID mycipher. With this in place, data will be encrypted and decrypted using mycipher instead of the default.