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 ofmodel
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 encryptedYour 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 |
---|---|
| The timestamp of when the key was created. The most recent key is what will be used for encryption. |
| Reference to the specific cipher class that handles the encryption. |
| 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 |
---|---|---|
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.