先日『暗号技術入門』を読みました。この過程で、アプリケーション開発で使用機会の多いbcryptについて気になり、調べてみました。
本記事では、bcryptを仕事で使う際に知っておいたほうが良さそうなことをまとめます。
環境
bcryptの挙動を説明するためにbcrypt-rubyを利用しています。
記事執筆時点で最新のv3.1.16を対象としています。
bcryptとは
bcryptは一方向ハッシュ関数のアルゴリズムの1つであり、(パスワードなどの)データからハッシュ値を算出します。
bcryptの特徴として、ハッシュ値から元のデータを逆算されづらくするために、ソルトとコストがあります。
一方向ハッシュ関数とソルト、コストについては後述します。
ちなみに、「パスワードをハッシュ化する」という表現をよく見かけますが、これは「ハッシュ関数を用いてパスワードからハッシュ値を得る」と同義です。また、ハッシュ値は「ダイジェスト」と表現することもあります。Rails Tutorialでは、この表現でしたね。
余談が長くなりましたが、bcrypt-rubyでは、このようにハッシュ値を計算できます。
BCrypt::Password.create('password')
# => "$2a$12$0jExrhEvd3zt6Me6OOj7xu7lysiIvPgixN1zExHgN6It.W4d4y/7C"
一方向ハッシュ関数とは
一方向ハッシュ関数とは、データからハッシュ値を計算することは出来るものの、ハッシュ値からデータに逆算ことはできない関数です。この逆算できない性質を、一方向性と呼びます。
ハッシュ値からデータに逆算ことができないため、パスワードが正しいかを判定するには、ハッシュ値同士で比較します。
hash = BCrypt::Password.create('password')
# => "$2a$12$smCjRIHIJt4yL1ZGPx2Xg.O0zVnDVXuR05Fz54T0bYlaEIlgor15K"
hash == 'password'
# => true
上記では、文字列同士を比較しているように見えますが、これはStringクラスを継承したBCrypt::Passwordクラスが、==
をオーバーライドしているためです。実装は、引数のsecretからハッシュ値を計算し、親クラスのString#==
を呼び出しています。
# https://github.com/bcrypt-ruby/bcrypt-ruby/blob/master/lib/bcrypt/password.rb
module BCrypt
# something...
class Password < String
def ==(secret)
super(BCrypt::Engine.hash_secret(secret, @salt))
end
end
end
レインボーテーブル攻撃とは
ソルトについて説明する前に、ソルトの理解に欠かせないレインボーテーブル攻撃について説明します。
レインボーテーブル攻撃は、事前に、膨大な数のパスワード候補の平文とそのハッシュ値をデータベースに保存しておき、ハッシュ値からパスワードの平分を取得する攻撃のことです。
ソルトとは
ソルトは、乱数であり、レインボーテーブル攻撃の対策のために必要です。
bcryptはソルトと平文を入力に取り、ハッシュ値を算出します。
ソルトを加えたデータのハッシュ値を計算することにより、このハッシュ値からデータを逆算するためには、ソルトのビット長だけ必要なデータ量は増えることとなり、レインボーテーブル攻撃に有効となるわけです。
bcryptでは、ハッシュ値の中にソルトが含まれていることを確認できます。
hash = BCrypt::Password.create("password")
=> "$2a$12$9Plx9x2SgweSioLq9UInwe6mrIuGzi0QzoZyVP46WDo45ata5ITLi"
pry(main)> hash.salt
=> "$2a$12$9Plx9x2SgweSioLq9UInwe"
bcrypt-rubyでは、Bcrypt::Passwordクラスにsaltメソッドが定義されています。
saltはソルトに加えて、バージョンとコストを含めて返します。
(上記の場合は、バージョンが、2a
、コストが12
、ソルトがUPUKgRo8aH/NfJ87TwJMJe
)
ストレッチングとコストとは
次に、ストレッチングについて説明します。
ストレッチングは、ハッシュ値への計算を繰り返し行うことです。ハッシュ関数によって出力されたハッシュ値を再度ハッシュ関数の入力にしてハッシュ値を得る、というサイクルを繰り返すわけです。
ストレッチングによって、ハッシュ値から平文を逆算するのに必要な計算時間が増え、ハッシュ値からデータを逆算されずらくなります。
このストレッチングの回数をコストと呼びます。コストを増やすほどハッシュ化が遅くなりパフォーマンスは悪くなりますが、ハッシュ値から逆算される時間も遅くなります。
bcrypt-rubyのデフォルトのコストは12です。
BCrypt::Engine.cost
# => 12