Data Encryption has never been so important to modern-day applications as it is today. Storing personal data in plain-text format makes the application open to data theft. Not only users' personal data is at risk, but even the application becomes subject to scrutiny when it falls under General Data Protection Regulation (GDPR) norms.
In this blog post, we will explore a quick way to encrypt & decrypt model attributes using Rails handy ActiveSupport::MessageEncryptor
class to ensure compliance such as GDPR.
Let's start with some basics on PII and GDPR mandates.
Personally Identifiable Information (PII) examples as per GDPR
PII is any data that could potentially identify a specific individual. Any information that can be used to distinguish one person from another, deanonymize, and personally identify an Individual.
Read more in detail here
Example list of PII data, which are commonly available to any Application are:
- Full name
- Email address
- Street address
- Phone number
- Credit card number
- IP address
Is Data Encryption mandatory to follow GDPR norms?
It's not a mandate to use encryption. As long as the application does not share any PII, it's all fine. If the application collects data over the internet (e.g. blogs or articles), such as their email addresses, etc., then the application must be under a moral obligation to protect the PII shared by the public audience. Encrypting data ensures that the application employs safety measures against the incidents of a data breach as the data would be meaningless even if leaked.
How to achieve Data Encryption?
There are popular gems like attr_encypted. The gem extends the Rails framework to allow us to encrypt data without much hassle.
Here we will see how to encrypt & decrypt model attributes without using any gem. You may then decide to either use a gem or use such custom approaches.
Attributes API + ActiveSupport::MessageEncryptor
are the two key components you will need.
Let's get started with actual implementation now:
Create a mechanism for Encryption
encryption_key = SecureRandom.hex(32)
@cryptor = ActiveSupport::MessageEncryptor.new(encryption_key[0..31], encryption_key)
The ActiveSupport::MessageEncryptor
expects two arguments: The first argument to be a string of size 32 bytes same as the length of our encryption_key
. The second argument is the signature, which is used by MessageVerifier
internally. For further info, refer message_encryptor and Github Issue
Test your changes:
my_secret_data = @cryptor.encrypt_and_sign('my secret random data')
# => "MVhQWVo3bFRGZTNXZWFrMDZBODRRTExBZlBXNW1oWFdBbTlQSVlxS3NCbz0tLUdMczUyaHU2aFRxYmhYZS9DeU96L1E9PQ==--4887eccce0d74af2093ccf8e093130cfb8de3767"
@cryptor.decrypt_and_verify(my_secret_data)
# => "my secret random data"
Applying Encryption with new Attribute Type
# config/initializers/encrypted_text_type.rb
class EncryptedTextType < ActiveRecord::Type::Text
def initialize(encryption_key: '')
# https://github.com/rails/rails/issues/25448#issuecomment-441832416
@cryptor = ActiveSupport::MessageEncryptor.new(encryption_key[0..31], encryption_key)
end
def deserialize(encrypted_value)
@cryptor.decrypt_and_verify(encrypted_value) if encrypted_value
end
def serialize(plain_value)
@cryptor.encrypt_and_sign(plain_value)
end
end
ActiveRecord::Type.register(:encrypted_text, EncryptedTextType)
The mechanism of @cryptor
from the previous example is just embedded into a new ActiveRecord Attribute Type. It is registered as encrypted_text
using the register method.
The example above will Serialize & Encrypt data before saving it to Database. And Deserialize & Decrypt the encrypted data when fetching from the Database. To know more about Attributes API, refer to this link.
Update the model that needs Encryption
# app/models/public_form_detail.rb
# == Schema Information
# id :integer(4) not null, primary key
# contents :text
# ...
class PublicFormDetail < ApplicationRecord
# ...
attribute :contents,
:encrypted_text,
encryption_key: Rails.application.secret_key_base
# ...
end
The PublicFormDetail
now has attribute definition on contents
of type encrypted_text
. Any value passed to contents will be processed and stored in the encrypted format in the Database. When accessing, encrypted string value will be decrypted and applied to the contents
attribute.
Verification
form_detail = PublicFormDetail.first
form_detail.update(contents: { email: 'iamironman@starktech.com', phone: '212-970-4133' })
form_detail.contents
=> "{:email=>\"iamironman@starktech.com\", :phone=>\"212-970-4133\"}"
form_detail.contents_before_type_cast
=> "RVYxQ0JRa21QNnVobkJUaWR1VmM4RVJTamtqWnV0ZkpmVElISVN0a3hab2txd3ZkRGIweHBObkdLbW84SUQwb3pWNEgyeDQ0RCtWZnJTMUM0VlExa3c9PS0tRnFsZk5yZWV2R3FFRHV2c1ZSbE9nQT09--cc67c8966b5a591e3e4795cb6e5b7483283a4753"
Do you prefer to encrypt & decrypt using such an approach or you like to use a gem? Or do you recommend any other approach?
Feel free to continue this/any topic on Ruby on Rails with me @ImaKaranV / LinkedIN (Karan Valecha)
Useful Links