Rails: 文字列型カラムの暗号化

外観

目的
MessageEncryptorでActiveRecordの文字列型カラムを暗号化します。

環境
macOS 10.15.5
Ruby 2.7.1
Rails 6.0.3.2
Yarn 1.22.4
Node 13.12.0

参照
ActiveSupport--MessageEncryptor
Railsで透過的にカラム暗号化 - Qiita

リポジトリ
https://github.com/usutani/try_crypt

モデル

画像2

addressとnameを暗号化します。

手順

rails new -TM --skip-active-storage try_crypt
cd try_crypt

saltを秘匿情報に追加します。
bin/rails secret | pbcopy
EDITOR="vim" bin/rails credentials:edit
salt: <bin/rails secret で生成した文字列>
bin/rails credentials:show

コンソールで動作確認します。
bin/rails c

# MessageEncryptorを生成する。
salt = Rails.application.credentials.salt
key_len = ActiveSupport::MessageEncryptor.key_len
key = Rails.application.key_generator.generate_key(salt, key_len)
crypt = ActiveSupport::MessageEncryptor.new(key)

# 暗号化した文字列を復号できることを確認する。
encrypted_data = crypt.encrypt_and_sign('my secret data')
crypt.decrypt_and_verify(encrypted_data)

bin/rails g scaffold Member name address
bin/rails db:migrate

class Member < ApplicationRecord
  after_initialize :decrypt_columns
  before_save :encrypt_columns

  def decrypt_columns
    return if new_record?

    crypt = message_encryptor
    self.name = crypt.decrypt_and_verify(name)
    self.address = crypt.decrypt_and_verify(address)
  end

  def encrypt_columns
    crypt = message_encryptor
    self.name = crypt.encrypt_and_sign(name)
    self.address = crypt.encrypt_and_sign(address)
  end

  def message_encryptor
    salt = Rails.application.credentials.salt
    key_len = ActiveSupport::MessageEncryptor.key_len
    key = Rails.application.key_generator.generate_key(salt, key_len)
    ActiveSupport::MessageEncryptor.new(key)
  end
end

bin/rails c -s

m = Member.create!(name: 'foo', address: 'bar')
m.name # => "暗号化された文字列"

m.reload
m.name # => "foo"
m.name_was # => "暗号化された文字列"
m.name = 'BAZ'
m.save

m.reload
m.name # => "BAZ"

モジュール化してみます。※後学のために

module ColumnEncryptor
  extend ActiveSupport::Concern

  included do
    after_initialize -> { decrypt_columns(crypt_col_names) }
    before_save -> { encrypt_columns(crypt_col_names) }
  end

  def crypt_col_names
    raise NotImplementedError
  end

  def decrypt_columns(col_names)
    return if new_record?

    crypt = message_encryptor
    col_names.each do |col_name|
      self[col_name] = crypt.decrypt_and_verify(self[col_name])
    end
  end

  def encrypt_columns(col_names)
    crypt = message_encryptor
    col_names.each do |col_name|
      self[col_name] = crypt.encrypt_and_sign(self[col_name])
    end
  end

  def message_encryptor
    salt = Rails.application.credentials.salt
    key_len = ActiveSupport::MessageEncryptor.key_len
    key = Rails.application.key_generator.generate_key(salt, key_len)
    ActiveSupport::MessageEncryptor.new(key)
  end
end

class Member < ApplicationRecord
  include ColumnEncryptor

  def crypt_col_names
    %i[name address]
  end
end

bin/rails s

画像1

以上です。

この記事が気に入ったらサポートをしてみませんか?