Edited at

PHPのPassword_hash関数をcrypt関数で再現する(メモ)


暗号学的ハッシュ化 Blowfishに詳しくなりたい!


PHPのpassword_hashを詳しくみる

PHPのpassword_hash関数の暗号学的ハッシュ化アルゴリズムがblowfishというのはPHPerなら誰でも知っていると思います。

その詳細を調べたらパスワードごとにsaltを個別に持つことが可能でかつ、DB内に個別にsalt用カラムを保つ必要がないということをマニュアルを読んで知ったのメモ。


$hoge = "password";
$hash = password_hash($hoge, PASSWORD_BCRYPT);
// 暗号学的ハッシュ化されたパスワード:$2y$10$FCcP.mohAJhrvV0LeAgExutz.wPmInQ9R3Pme89QBVnJmujpZ4Ygi

if (password_verify($hoge, $hash)) {
echo("認証");
}

本来であれば、何も迷わずpassword_hashを使えばいいのでしょうけど、

crypt関数でも代用可能だと知ってちょっとためしてみました。


$hoge = "password";
$algo = '$2a$04$dummysalt/upto22characters';
$hash = crypt($hoge, $algo);
// $2a$04$dummysalt/upto22charaOlqk5u.LapVt7D9UokdVw2kezh9.edIq

// blowfishのsalt形式は3つ目の$を含めた以降の文字列のサイズが22byteであることが
// 仕様であるため、23文字目以降はむしされる模様。
// つまり、暗号学的ハッシュ化された文字列を第二引数に渡すことで同じhash値を計算することができる
// という仕様のようです。
if(crypt($hoge, $hash) === $hash) {
echo ("認証");
}

blowfishで暗号学的ハッシュ化するためには規定のSALT形式があるのでその仕様をみると


blowfish salt形式のフォーマット

引用リンク

Blowfish 仕様について

CRYPT_BLOWFISH - Blowfish ハッシュ。salt の形式は、 "$2a$" か "$2x$" あるいは

"$2y$"、2 桁のコストパラメータ、"$"、そして文字 "./0-9A-Za-z" からなる 22 文字となります。

この範囲外の文字を salt に使うと、crypt() は長さゼロの文字列を返します。

2 桁のコストパラメータは反復回数の 2 を底とする対数で、 これは Blowfish

ベースのハッシュアルゴリズムで使います。 この値は 04 から 31 までの範囲でなければならず、

それ以外の値の場合は crypt() は失敗します。

5.3.7 までのバージョンの PHP では、salt のプレフィックスとして "$2a$" だけしか使えませんでした。

PHP 5.3.7 で新たなプレフィックスが導入され、 Blowfish の実装にあったセキュリティ上の弱点に対応しました。

セキュリティ修正の対応の詳細については » この文書 を参照ください。

簡単にまとめると、PHP 5.3.7 以降しか使わないのなら "$2a$" ではなく "$2y$" を使うべきだということです。

特に、22文字の長さに指定されているsalt値は、末尾の文字(22番目)が 調べたところ(e|u|O|.)の4種類のうちどれかである必要があるようです。(※理由は現在未調査。)

salt値を任意に作成すると結果のハッシュ値には4種のうちどれかに置換されている。なぜ??

この仕様に則ってsaltをいろいろと変更してみましたが、

\$(2a)\$(04)\$とある(04)のコストパラメータをマックスの31にするとまったく計算が

完了せず使い物になりませんでした。

よっぽどの強固な暗号が必要で無い限りということでしょうけど、こんなに戻り値が

帰ってこないのに、使う場面あるの?とも思いましたが。

やはり、最小の04でやるとすんなり計算がおわるので、特に考えずに04を使っていこうと

思いました。

さて、このcrypt関数で作成したハッシュ値もpassword_verify関数に渡してやることで

パスワードの成否を検証することができるようです。


to_verify.php

$hoge = "password";

$algo = '$2a$04$dummysalt/upto22characters';
$hash = crypt($hoge, $algo);

if(password_verify($hoge, $hash)) {
echo ("true");
}



次に、NodeJSからPHP上でパスワード認証

では、次にNodejsを使用して任意のパスワードを暗号学的ハッシュ化します。

それをPHP側の検証関数であるpassword_veryify関数を使って認証処理を行います。

まずはNodejs側のコード


to_hash.js

var bc = require("bcrypt");

var rounds = 11;
// 暗号学的ハッシュ化前の平文パスワード
var plain = 'br[/.:uiy8346h2lilfsfy\\[]:;,j@;phjdifj.;@:[d:f;@sdfferff';
var hash = null;
bc.genSalt(rounds, function (error, salt) {
// このsalt変数は前述のblowfish salt形式仕様を参照のこと
console.dir("[INFO]blowfishに使用する salf => " + salt);
//'[INFO]blowfishに使用する salf => $2b$11$s7fk.SuG4ImgQVQApvZw9.'
bc.hash(plain, salt, function (error, __hash) {
hash = __hash;
console.dir("[INFO]暗号済みパスワード => " + hash);
//'[INFO]暗号済みパスワード => $2b$11$s7fk.SuG4ImgQVQApvZw9.zUpeECKRKSQtOYsq9UECjSgb9FWXsZy'
});
});

// hashに使用された salt => $2b$11$.UTPU5.xkvYHsj7Lfz7Mnu
// hash化されたパスワード
// [$2b$11$8oB.24B5qhvnn5abmNEgze7LgUdf6Tz6Rg6/7LhVM467LGHLJ4hwq]
// をPHPのpassword_verify関数で認証してみる。



to_verify.php


$hash = '$2b$11$8oB.24B5qhvnn5abmNEgze7LgUdf6Tz6Rg6/7LhVM467LGHLJ4hwq';
$plain = 'br[/.:uiy8346h2lilfsfy\\[]:;,j@;phjdifj.;@:[d:f;@sdfferff';

if (password_verify($plain, $hash)) {
print("[INFO]認証成功");
} else {
print("[INFO]認証失敗");
}


以上の用に、Nodejsで暗号学的ハッシュ化されたパスワードもPHP側で認証することもできるようになりました。