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