PII (personal identifiable information) and PHI (personal health information) is considered very sensitive, and if you are processing it, there can be requirements to protect it.
Why you need attribute encryption:
- If someone steals your database dump, they won’t have easy access to the encrypted attributes (they will also need the encryption keys)
- Encrypted columns are automatically filtered in logs
Security-oriented static code analysis tools like bearer/bearer can hint on what attributes you should encrypt:
1. Encrypt attributes
If you run this command in the console
it will generate a few lines, that you should add to
active_record_encryption: primary_key: 1qRx9LKs1ON5gbk0q5Affs898O0S0sXo deterministic_key: pCgz9AgTkwO8zcn3hrZBL6tbNVQyxGvL key_derivation_salt: pOIy8FEWO3hVpt1f05LKuWETU1uOICPb
Try to encrypt attributes:
# app/models/client.rb class Client < ApplicationRecord encrypts :name, deterministic: true, downcase: true # string encrypts :annual_income # integer encrypts :date_of_birth # datetime encrypts :health_condition # text has_rich_text :description, encrypted: true # ActionText end
2. Problems that you will encounter:
2.1. Encryptable data types:
You can really encrypt only
text fields (because we store a long hashed string on the database level).
It is recommended to store encryptable attributes as
If you want to encrypt an
datetime, you will get errors. You have to store encryptable data as
I think it is quite safe to change column type from integer to text. If you do so, further encryption will be easy.
class StoreIntegersAsText < ActiveRecord::Migration[7.0] def change change_column :clients, :annual_income, :text end end
2.2. Querying encrypted data:
Only if you use deterministic encryption, you will be able to query the database, but only for an exact match like
If you want to add encryption to existing attributes that already store data, you will get an error.
Add these lines to
application.rb to allow encrypted and unencrypted data to co-exist:
# config/application.rb config.active_record.encryption.support_unencrypted_data = true config.active_record.encryption.extend_queries = true
3. Useful commands
client = Client.last # encrypt all attributes that use encrypts client.encrypt # decrypt all attributes that use encrypts client.decrypt # get the value that is stored in the database, not the decrypted version client.ciphertext_for :sexual_orientation # is this attribute encrypted? client.encrypted_attribute? :sexual_orientation # encrypt all records Client.all.map(&:encrypt) # decrypt all records Client.all.map(&:decrypt)
- Encrypting attributes adds a layer of complexity, but sometimes external factors force you to do it.
- I currently use Active Record Encryption to encrypt
ApiToken.secret_tokenin an app.