Closed6

[ActiveRecord]カラムの暗号化

utmutm

Active Record Encryption

  • Rails7系から使用可能(なので今回は要件外)
  • 宣言的に使用できる
class Inquiry < ApplicationRecord
  encrypts :email
end
  • 発行されるSQLは以下のように暗号化されている
> inquiry = Inquiry.create(email: "encrypted@example.com")
  TRANSACTION (0.2ms)  BEGIN
  Inquiry Create (1.0ms)  INSERT INTO "inquiries" ("email") VALUES ($1) RETURNING "id"  [["email", "{\"p\":\"q8wrMdpuKWGgRcTMqx9OdUbHISCJ\",\"h\":{\"iv\":\"kcaByOC0JWLrzup6\",\"at\":\"XGOMVg2FvHhPn8uNCn6XSw==\"}}"]]
  TRANSACTION (0.7ms)  COMMIT
  • console上では属性名で呼び出せて透過的
> inquiry.email
=> "encrypted@example.com"
  • 暗号化にはActiveSupport::MessageEncryptorの直接的な使用はされていない(コメント参照)
        def encrypt(clear_text)
          # This code is extracted from +ActiveSupport::MessageEncryptor+. Not using it directly because we want to control
          # the message format and only serialize things once at the +ActiveRecord::Encryption::Message+ level. Also, this
          # cipher is prepared to deal with deterministic/non deterministic encryption modes.

          cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
          cipher.encrypt
          cipher.key = @secret

          iv = generate_iv(cipher, clear_text)
          cipher.iv = iv

          encrypted_data = clear_text.empty? ? clear_text.dup : cipher.update(clear_text)
          encrypted_data << cipher.final

          ActiveRecord::Encryption::Message.new(payload: encrypted_data).tap do |message|
            message.headers.iv = iv
            message.headers.auth_tag = cipher.auth_tag
          end
        end

セットアップ

  • creadentialにmaster_key登録する
$ bin/rails db:encryption:init
Add this entry to the credentials of the target environment:

active_record_encryption:
  primary_key: EGY8WhulUOXixybod7ZWwMIL68R9o5kC
  deterministic_key: aPA5XyALhf75NNnMzaspW7akTfZp0lPY
  key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz

参考

utmutm

attr_encrypted

  • gem attr_encrypted
  • メンテナンスが止まっている
  • 使用する際は, 暗号化したいカラムにつき、2つのカラムが必要(encrypted_xxx, encrypted_xxx_iv)
  create_table :users do |t|
    t.string :name
    t.string :encrypted_ssn
    t.string :encrypted_ssn_iv
    t.timestamps
  end
  class User
    attr_encrypted :ssn, key: 'This is a key that is 256 bits!!'
  end

Database columns in version 4.x and older
Versions 4.x and older stored the OTP secret in an attribute called encrypted_otp_secret using the attr_encrypted gem. This gem is currently unmaintained which is part of the motivation for moving to Rails encrypted attributes. This attribute was backed by three database columns:

encrypted_otp_secret
encrypted_otp_secret_iv
encrypted_otp_secret_salt

Two other columns were also created:

consumed_timestep
otp_required_for_login

A fresh install of 4.x would create all five of the database columns above.
(引用: UPGRADING.md)

  • 内部的には、OpenSSL::Cipherが使用されていて、その処理のgem encryptorもある
utmutm

crypt_keeper

class MyModel < ActiveRecord::Base
  crypt_keeper :field, :other_field, encryptor: :active_support, key: 'super_good_password', salt: 'salt'
end

model = MyModel.new(field: 'sometext')
model.save! #=> Your data is now encrypted
model.field #=> 'sometext'
  • 内部的にはActiveSupport::MessageEncryptorが使われている
      def initialize(options = {})
        key  = options.fetch(:key)
        salt = options.fetch(:salt)

        @encryptor = ::ActiveSupport::MessageEncryptor.new \
          ::ActiveSupport::KeyGenerator.new(key).generate_key(salt, 32)
      end
utmutm

lockbox

  • gem lockbox
  • 暗号化したいattributeにciphertextのsuffixをつけたカラムを追加する
  • 継続的にメンテナンスされている(2022/10/09現在)
class AddEmailCiphertextToUsers < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :email_ciphertext, :text
  end
end
class User < ApplicationRecord
  has_encrypted :email
end
  • 発行されるSQLは以下のような形で暗号化されており、返り値はemail: [FILTERED]になる
> user = User.create!(email: "encrypted@example.com")
  TRANSACTION (0.2ms)  BEGIN
  User Create (3.9ms) INSERT INTO "email" ("email") VALUES ($1) RETURNING "id"  [["email_ciphertext", "gq+5PVbKxR7D7yZU18J5Dkg4pPu0nGYyVkuj1KAT5d85bEjhmYx2h6nVAoWBejOmdh3AHtHCuJCzEogL"]]
  TRANSACTION (0.7ms)  COMMIT
=> #<User id: 1, email: [FILTERED]">
  • console上では属性名で呼び出せて透過的
> User.email
=> "encrypted@example.com"
  • 暗号化にはBase64が使用されている
    def encrypt(message, **options)
      message = check_string(message)
      ciphertext = @boxes.first.encrypt(message, **options)
      ciphertext = Base64.strict_encode64(ciphertext) if @encode
      ciphertext
    end

セットアップ

  • Key Generation
> Lockbox.generate_key
  • creadentialsにmaster_keyを保存
lockbox:
  master_key: "0000000000000000000000000000000000000000000000000000000000000000"
utmutm

active_record_encryption

class PointLog < ActiveRecord::Base
  encrypted_attribute(:date, :date)
  encrypted_attribute(:point, :integer, default: -> { Current.user.current_point })
  encrypted_attribute(:price, Money.new)
  encrypted_attribute(:serialized_address, :string)

  # Change encryptor
  encrypted_attribute(:name, :field, encryption: { encryptor: :active_support, key: ENV['ENCRYPTION_KEY'], salt: ENV['ENCRYPTION_SALT'] })
end
  • 内部的にはActiveSupport::MessageEncryptorが使われている
      def initialize(key:, salt:, cipher: nil)
        key_generator = ::ActiveSupport::KeyGenerator.new(key)
        @encryptor = ::ActiveSupport::MessageEncryptor.new(key_generator.generate_key(salt, 32), cipher: cipher)
      end
このスクラップは2022/10/30にクローズされました