How we secure sensitive data using Rails MessageEncryptor, from plain attributes to secured one.
This article combined from store salt beside hashed attributes and article from Pawel urbanek about message encryptor.
For this example each salt is located in same record encrypted attribute. It use a salt ‘works’ by making sure the hash result unique to each used instance. This is done by picking a different random salt value for each computed hash.
MessageEncryptor is rails function which have ability to encrypt decrypt value use base64 encoded. It using key which generate from combination password
, salt
and length
. For now it use ActiveSupport::MessageEncryptor.key_len
which have 32 since ruby from version 2.4.0 forward use open ssl which have 32 bytes length.
Service to encrypt and decrypt
https://gist.github.com/kusumandaru/03a6b220ed3706dc5c49dd2a4b48b167
It define initialise, so each new method will create encryptor/decryptor based on salt record. Then it can encrypt or decrypt attribute.
It read key ENCRYPT_KEY_BASE
from environment variable, make sure key not uploaded to repository and try to make unique for production and staging environment. Also create using hard random key like SecureRandom.base64 or SecureRandom.random_bytes
Define concern
https://gist.github.com/kusumandaru/356de4bf1c5115589a80465b5bec5683
We use meta programming ruby to override class method attr_encrypted
for each attribute which assigned and and assign value to encrypted_attribute
Example: attr_encrypted :mother_name
, on_model user, it will overriding attribute mother_name
with decrypted value and reassign value for attribute encrypted_mother_name
with encrypted value. You can assign multiple attribute also on these class method
It send salt from record and encrypted_attribute to encryption service, either to encrypt or decrypt data.
When salt not exist on those record, it also built automatically use Active
ActiveSupport::MessageEncryptor.key_len a.k.a SecureRandom.random_bytes(32)
Migrate plain attributes to encrypted one
https://gist.github.com/kusumandaru/054b3dd7ed8d10ea75ce0e9efb45ca30
After service and model already done we try to migrate unencrypted data, we define new column with prefix encrypted_ and we add salt with type binary, in postgres it will save with format bytea. Cause salt cannot save in format string.
We also define user.mother_name = user.mother_name_was
since method mother_name already override on above concern, and mother_name_was is directly call active record.
For better security try to store salt in different source. Like different database, or access by another endpoint, so when database was breach, key is on another world.
Alternatively you can use third party cryptography service like aws/google cloud KMS, vault, or thales.