Skip to content

Encrypt decrypt Rails attribute using MessageEncryptor

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 ActiveActiveSupport::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.

https://unsplash.com/photos/0Yiy0XajJHQ

Leave a Reply

Your email address will not be published. Required fields are marked *