やりたいこと
bcryptを使って、新規ユーザー登録時におけるパスワードのハッシュ化と、ログイン時におけるパスワードの認証を実現したい。
前提
・パスワードは平文で保存するのはNG。
・セキュリティ上、ハッシュ値に変換して保存する。
そもそも暗号化とハッシュ化の違いを理解できていなかったのでメモります。
その違いとは、不可逆性があるかどうか。
暗号化は不可逆性がある。つまり、暗号化した後に元に戻せる。
ハッシュ化は不可逆性がない。つまり、一度ハッシュ化すると元に戻せない。
方法
パスワードの生成にはBCrypt::Password.create
パスワードの認証にはBCrypt::Password.new
を利用する
新規ユーザー登録でやりたいこと
・入力した生パスワードをハッシュ化して保存する。
Railsには以下のGemがデフォルトで入っているのでそれを利用する。
gem 'bcrypt', '~> 3.1.7'
パスワードの生成にはBCrypt::Password.create
を利用する。
以下のように使うと、入力値がハッシュ化された様子がわかる。
使い方
BCrypt::Password.create('入力した生パスワード')
使用例
> hashed_password = BCrypt::Password.create('password')
=> "$2a$12$jA9fyFy1qfh379BEfaJM2uGPe9EnqrEZnREv1iaiX8nyCmQz69ERK"
ログイン時にやりたいこと
パスワードの認証にはBCrypt::Password.new
を利用する。
「DBに保存されているハッシュ化されたパスワード」と
「打ち込んだパスワードをハッシュ化したもの」を比較(認証)する。
以下のように使う
使い方
> BCrypt::Password.new(DBのハッシュ化されたパスワード) == '打ち込んだ生パスワード'
使用例
> BCrypt::Password.new(hashed_password) == 'password'
=> true
これにはエイリアスis_password?()
が存在し、以下の書き方もできる。
> BCrypt::Password.new(hashed_password).is_password?('password')
=> true
冒頭でも書いた通り、ハッシュ化されたパスワードは復元できないので、
入力値をハッシュ化させて比較する方法になります。
注意
面白いのは左辺と右辺を逆にした場合で結果が違うということ。(左右逆にすると通らない)
必ず、上記の順番で記述することがポイント
> 'password' == BCrypt::Password.new(hashed_password)
=> false
なぜなら、
BCrypt::Password.new(hashed_password) ==
の==
はメソッドとして定義されているから。
BCryptのソースコードを見ると以下の記述があります。
入力した、パスワードをハッシュ化させて、ソルト(ランダムな文字列)を付加しています。
def ==(secret)
super(BCrypt::Engine.hash_secret(secret, @salt))
end
alias_method :is_password?, :==
認証の仕組みはどうやって行うのか
パスワードのハッシュ化はハッシュ関数を通すことで実現されています。
ハッシュ関数は「同じ値をこの関数に通すと同じハッシュ値を得られる」という性質を持ちます。つまり、ハッシュ値に対してパスワードの総当たり攻撃をかけると力技で元のパスワードを特定できてしまう。
そこで、その対策としてハッシュ値に対し、ソルトと言われるランダムな文字列を付加して、ハッシュ化パスワードとしてDBに保存される仕組みが生まれました。
認証の際には、DBのハッシュ化パスワードの中からソルトの部分を取り出し、入力した生パスワードのハッシュ値に連結させます。そして、DB上のハッシュ化パスワードと付き合わせる仕組みだそうです。
以下でわかりやすく解説されていました。
bcrypt-rubyについて調べたメモ
備考
パスワードの再設定が必要な理由は、パスワードの不可逆性にあります。
「あなたのパスワードは〇〇です。」と返してくれたらいいのになぁと思っていましたが、それは難しいそうです。復元できないからこそ再設定という機能が必要になったと・・・