deviseの暗号化アルゴリズムの変更方法
認証を、Rails組込みのhas_secure_passwordからdeviseに変更した時に、なにか変更があるかと思ったが、 どちらもbcryptを使っているのでそのまま移行できた(bcryptはダイジェストにストレッチの情報を含むため)。 ただ、せっかく調べたのでメモとして残しておく。
deviseの暗号化処理を変更する
暗号化処理を変更する対象はなんでも良いが、bcryptを使って実装してみる。(実際、deviseはもともとbcryptを使って実装してあるのでbcryptで再実装する意味は無い。)
また、SHA1等の処理は用意されているためこちらを参考。
まず、devise-encryptable のgemをインストールする。
READMEの通りにする。
# deviseの引数に :encryptable を追加する。 class User < ActiveRecord::Base devise :database_authenticatable, :encryptable end
# password_saltカラムを追加するため、マイグレーションファイルを作成 class DeviseCreateUsers < ActiveRecord::Migration def change add_column :users, :password_salt, :string end end
rake db:migrate を実行する。
devise-encryptableをカスタマイズする
devise-encryptableで定義しているメソッドを再定義する。
まず、暗号化の処理をdigestメソッドに書く。このメソッドは暗号化後の文字列を返す必要がある。また、引数については以下のようになっている。
- password : 入力されたパスワード
- stretches : stretchesに設定された値、設定するにはdeviseをインクルードしたクラスに
devise :database_authenticatable, stretches: 20
などとして設定する。stretchesには通常、ハッシュ関数をパスワードに適用する回数が設定される。 (bcryptではcostとなっているが、2のcost乗の回数分だけハッシュ関数を適用しているだけなのでstretchesをcostに設定すれば良い。) - salt : 暗号の強度を増すためにパスワードに付加する文字列。
- pepper : saltと同じ役割
例としてbcryptを使った物を書いておく。
# config/initializers/devise_encryptor.rb module Devise module Encryptable module Encryptors class PasswordAuthentification < Base def self.digest(password, stretches, salt, pepper) # has_secure_passwordからそのまま持ってきただけなのでpasswordしか使っていない... cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine::DEFAULT_COST BCrypt::Password.create(password, cost: cost) end end end end end
また、サインインの時にはself.compareが呼ばれる。このメソッドはdevise-encryptableで以下のように定義されている。
def self.compare(encrypted_password, password, stretches, salt, pepper) Devise.secure_compare(encrypted_password, digest(password, stretches, salt, pepper)) end
Devise.secure_compareは2つの引数(文字列)が等しい場合にtrueを返す。 SHA1等の場合はdigestメソッドの戻り値とencrypted_passwordが等しくなるのでこれで問題ないが、 今回使うbcryptは暗号化時にランダムな文字列を内部で生成し、それをソルトとして使うので暗号化処理行う度に暗号化後の文字列が変わる。 そのため既存のcompareでは比較ができないため、compareも再定義する。
# config/initializers/devise_encryptor.rb module Devise module Encryptable module Encryptors class PasswordAuthentification < Base def self.digest(password, stretches, salt, pepper) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine::DEFAULT_COST BCrypt::Password.create(password, cost: cost) end def self.compare(encrypted_password, password, stretches, salt, pepper) # BCrypt::Passwordでは 「==」メソッドが再定義されているので、これでパスワードの確認ができる BCrypt::Password.new(encrypted_password) == password end end end end end
最後に、config/initializers/devise.rb について
# config.encryptor = :sha512
となっている部分を変更する。
config.encryptor = :password_authentification
bcryptのメモ
bcryptにはcostを設定できるが、有効な値は4〜30までで、2のcost乗だけハッシュ関数の適用が繰り返される。
なお、costを4未満に設定すると強制的に4が設定され、costを31以上に設定すると例外が発生する。