ゴミ箱.net

汚物は消毒

DBに保存したパスワードのハッシュ化アルゴリズムがどんどん陳腐化する件

パスワード認証機能つきのウェブアプリケーションにおいて、入力されたパスワードを照合するために比較用のデータをどこかに持つ必要がある。
パスワードそのものを保存するのはもちろんご法度。なので適当なハッシュ関数を使ってパスワードのハッシュ値を計算し、それをデータベースに保存するようにしていた。

ところが肝心のハッシュ関数が衝突耐性が破られたとかでどんどん陳腐化していく。MD5、SHA1はもう死んだ。SHA256もそのうち死ぬだろう。
かといって、ハッシュ関数をある日突然差し替えたら、それ以前に計算したハッシュ値がことごとく役に立たなくなる。それはユーザに大迷惑なので避けたい。
ならばハッシュ値だけでなく、そのハッシュ値をどのハッシュ関数で計算したかもあわせて保存すればいいわけだが…
車輪を最発明すれば済むだけの話だがそれもわずらわしい。ハッシュ値、ソルト、ハッシュ関数の識別子をどういうフォーマットで保存すればいいのか毎回考えるのも面倒だ。何か標準化された方法はないものだろうか?

そんなことを考えていたら、Apache Commons Codecのクラスorg.apache.commons.codec.digest.Cryptとやらを使えば願いが叶うと聞いた。

String saltedHash = Crypt.crypt(password);
のように書けば、現時点での最強のハッシュ関数を使い、ランダムなソルトつきハッシュ値を生成してくれる。
「アルゴリズムの識別子を含めたソルト」を指定してハッシュ値を計算するには
String saltedHash = Crypt.crypt(password, salt);
だ。

おもしろいことに、上記で出力されたソルトつきハッシュ値自体がソルトとしても機能する。
saltedHash == Crypt.crypt(password, saltedHash);
が成立するので、これを使うことでパスワードの照合ができるのだ。saltedHashには使用したハッシュ関数の識別氏も含まれるため、自動的にハッシュ関数が選択される。

その種はこうだ。
このメソッドで生成したハッシュ値は「$ハッシュ関数の識別子$ソルト$本来のハッシュ値」という形式になる(ハッシュ関数にDESを使った場合は少し違うが些細なことなので置いておく)。
一例を挙げると「$6$fD87BDEQ$duvOn9aGmehUe/psF.nJvDGh7RTh9qC8SbxR7G9iZ1cYMtqKvZDWfM3YMiit18fV9gNzmKq4.RROV6S4fi/i.. 」のような文字列になる。6というのがハッシュ関数としてSHA512を使うことを示す識別子、fD87BDEQがランダムに生成されたソルト、duvOn9aGmehUe…以下が真のハッシュ値である。
メソッドCrypt.cryptの第2引数に上記のソルトつきハッシュを指定すると、2番目の$より後ろを切り捨てて「$ハッシュ関数の識別子$ソルト」の部分だけを残して処理に使うのだ。

なおCrypt.cryptには、「アルゴリズムおよびソルトを指定しないもの」と「『アルゴリズムの識別子を含めたソルト』を指定するもの」があるが、「明示的にアルゴリズムだけを指定するが、ソルトをランダム生成するもの」が存在しない。
たとえば、何らかの事情でSHA1やSHA256などが使えず、あえて旧世代のハッシュ関数であるMD5を使う必要がある場合に、ソルトをランダム生成してパスワードをハッシュ化したい、といった場合にはどうすればいいか。
そのような場合は、使いたいハッシュ関数に応じてクラスMd5Cryptのメソッドmd5Crypt(byte[])クラスSha2Cryptのメソッドsha256Crypt(byte[])sha512Crypt(byte[])などのメソッドをソルト生成用に流用することができる。
先ほど、計算したハッシュ値自体がソルトとして使えると書いたことを思い出してほしい。
これらのメソッドは特定のハッシュ関数を使い、引数で指定したデータに対して「ハッシュ関数の識別子とランダム生成されたソルトを含むハッシュ値」を出力するものである。そして、その値をCrypt.crypt(password, salt)にソルトとして与えることができる。元データによって決まる真のハッシュ値の部分はどうせ使わないので、元データは何でもよい。とりあえず長さ0の配列でも指定しておこう。

SHA512を明示的に指定してランダムなソルトを使ったハッシュ値を得る例を示す。
String salt = Sha2Crypt.sha512Crypt(new byte[0]);
String saltedHash = Crypt.crypt(password, salt);

ちなみにこのメソッド群は、もともとLinuxのcrypt関数を移植したもので、処理も互換性があるらしい。
試してはいないが、上記の方法で生成したハッシュ値はJavaだけでなく、crypt関数を利用したC言語などのプログラムでも同様に使えるのだと思われる。
スポンサーサイト

PageTop

コメント


管理者にだけ表示を許可する