個人情報を扱うサービスを考えると、どうしても特定のカラムを暗号化する必要が出てくることがある。
いくつかのgemを試してみて、どうもしっくりこなかったので、gemを作ることにした。
既存gemの紹介と、新しい暗号化gemの比較をしてみよう 👀
attr_encrypted
ActiveRecord + 暗号化 で調べると真っ先に出てくるgemのひとつ
定義をする側はシンプルなのだが、使う時になかなかクセがある。
暗号化したいカラムにつき、2つのカラムが必要になるようだ。
class Model < ActiveRecord::Base
attr_encrypted :field_name, key: ENV["KEY"]
end
# migrationは下記のような定義になる
create_table(:models) do |t|
t.string(:encrypted_field_name)
t.string(:encrypted_field_name_iv)
end
暗号化自体は、とてもシンプルに扱うことができる。
しかし、 #changes
の値が分かりづらかったり、 field_was
で過去の値を取得できないなどの問題がある。
特定のケースでおかしな挙動をするので、正直言うと実用に耐えない。
instance = Model.create(field: 'hello')
=> <Model id: 1, encrypted_field: "d9thb/Pvj6gMm2TPLHoola3aHsWy\n", encrypted_field_iv: "UfosEdeGJJmIBUHs\n">
instance.field
=> "hello"
# changes や _was など
instance.field = "hello world"
instance.changes_to_save
=> {"encrypted_field"=>["d9thb/Pvj6gMm2TPLHoola3aHsWy\n", "RxJTGKPG3PMi1NA0LY/tSu9r16sMWfalAFRc\n"],
"encrypted_field_iv"=>["UfosEdeGJJmIBUHs\n", "1lMAgBaLBrqQUPUU\n"],
"field"=>[nil, nil]}
instance.field_changed? #=> true
instance.field_was #=> nil
crypt_keeper
透過的暗号化を謳う暗号化gemだ。
serialize
の機能を活用して、内部実装はかなりシンプルにできている。
class MyModel < ApplicationRecord
crypt_keeper :field, encryptor: :active_support, key: ENV['KEY'], salt: ENV['SALT']
end
create_table(:models) do |t|
t.binary(:field)
end
そして、透過的暗号化を謳っているだけあって挙動もシンプル!!
#changes
などの挙動も正しく、問題なさそうである。
暗号形式の拡張も柔軟にできるため、通常使う分には十分実用に耐えうると感じた。
instance = Model.new(field: 'hello')
instance.save
instance #=> <Model id: 1, field: "hello">
instance.field #=> "hello"
instance.field = 'hello world'
instance.changes
# => {"field"=>["hello world", "hello new world"]}
active_record_encryption
今回作成した新しい暗号化gemである。
このgemの特徴は、Attribute APIを使っており、好きな型を扱えるようになった点である。
他のgemは、string型しか扱えない。
下記の通り、第二引数にて、好きな型を指定することができる。
また、 default:
引数でデフォルト値を設定することも用意である!!
class Model < ApplicationRecord
encrypted_attribute :field, :date
encrypted_attribute :field_2, Money.new
encrypted_attribute :field_3, :string, default: 'default value'
end
実際に使ってみると分かるのだが、だいたい他の挙動については、crypt_keeper
と同等に透過的である。
serialize
なども使える分、 crypt_keeper
よりも透過的かもしれない。
instance = Model.new(field: '2000/01/01', field_2: 100)
instance.field_1 #=> Sat, 01 Jan 2000
instance.field_2 #=> <Money value: 100>
instance.field_3 #=> 'default value'
まとめ
attr_encrypted
はあまりにも使いにくすぎるので、いずれのケースからも選択肢からは外れそうだ。
さようなら
String型のみ暗号化する場合は crypt_keeper
を使うだろう。
そして、様々な型のデータを暗号化したいケースの場合は、active_record_encryption
を使うといいだろう