LoginSignup
20
12

More than 5 years have passed since last update.

Railsで透過的にカラム暗号化

Last updated at Posted at 2018-06-11

個人情報を扱うサービスを考えると、どうしても特定のカラムを暗号化する必要が出てくることがある。
いくつかの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 はあまりにも使いにくすぎるので、いずれのケースからも選択肢からは外れそうだ。
さようなら :pray:

String型のみ暗号化する場合は crypt_keeperを使うだろう。
そして、様々な型のデータを暗号化したいケースの場合は、active_record_encryptionを使うといいだろう :smile: :thumbsup:

20
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
12