Closed6
[ActiveRecord]カラムの暗号化
要件
- 復号化できること
- string or text型で使用
- Rails6系で使用
- ActiveSupport::MessageEncryptorを生で使わずにgemを検討(参考: 可逆暗号化をする gem の lockbox のキーローテーションで躓いた件)
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
参考
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
- gem devise-two-factorでも使用されているが、Rails 7系以降ActiveRecord::Encryptionでwrapされている
https://github.com/tinfoil/devise-two-factor/pull/214
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もある
crypt_keeper
- gem 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
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"
active_record_encryption
- gem active_record_encryption
- 第二引数に型を取ることで、string型以外のカラムでも使用可能
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にクローズされました