web-dev-qa-db-ja.com

セキュリティレビュー-PHPのpassword_hash実装

現在、PHPコアの「ヘルパー関数」に取り組んでおり、大多数の開発者がパスワードハッシュをより安全かつ簡単に行えるようにしています。基本的に、目標はそれを非常に簡単にすることであり、コアのセキュアなものを使用するよりも独自の実装を発明することは困難です。また、ハッシュ方式の改善に伴い、将来的に更新および拡張されるように設計されています。

私は 追加用のRFC を作成し、 関数の実装 も用意しています。

基本的な前提は、大部分の開発者がcryptを直接使用するのは難しいことです。奇妙なエラーが返されたり、base64のような(ただし、別のアルファベットを使用する)ソルトなどを返します。これらの関数は、推測を排除し、単純なAPIを提供するように設計されています。

_string password_hash(string $password, string $algo = PASSWORD_DEFAULT, array $options = array())
bool password_verify(string $password, string $hash)
string password_make_salt(int $length, bool $raw_output = false)
_

Password_Hashは、パスワード、オプションのアルゴリズム指定子(現時点では、改良された_CRYPT_BCRYPT_実装のみがサポートされていますが、後でscryptをオプションとして追加したい)、およびオプション配列を受け取ります。オプション配列は、bcryptへのcostパラメータと、事前定義されたソルト値を指定できます。

Password_Verifyは、パスワードと既存のハッシュを受け入れます。次に、パスワードを再ハッシュします($tmp = crypt($password, $hash)と同じ)。次に、一定時間の比較関数を使用して、2つのハッシュが実際に等しいかどうかを判断します。

Password_Make_Saltは、指定された長さのランダムな文字列を生成するために存在します。 _raw_output_がfalse(デフォルト)に設定されている場合、出力される「塩」は、crypt()と直接互換性のある方法でbase64エンコードされます。 _raw_output_がtrueの場合、ランダムなフルバイト(_0-255_)を使用して同じ長さの文字列を返します。

例:

_$hash = password_hash("foo");
if (password_verify("foo", $hash)) {
    // always should be true
} else {
}
_

PHPのソースコードの読み取りに関する注意:PHP関数(phpコードに公開されているもの)はPHP_FUNCTION()マクロで囲まれています。さらに、php変数(zval)がそれらの一部にアクセスするマクロは

  • Z_TYPE_P()(zvalへのポインターの型を見つける)
  • Z_STRVAL_P()(文字列値へのポインタを取得)
  • Z_STRLEN_P()(文字列型のint長さを取得)
  • Z_LVAL_P()(整数型のlong値を取得)

さらに、zval_ptr_dtor()zvalの参照カウントを減らし、_0_に到達した場合にそれをクリーンアップするための参照カウントメカニズムです。

変更を正式に提案する前に、少なくとも数人のセキュリティ専門家による実装のレビューを探しています。それはかなり短いです(約300行のコードのみ)...

更新

APIが承認されたため、機能のプルリクエストを設定しました。お時間がありましたら見直してください。ありがとう!: https://github.com/php/php-src/pull/191

48
ircmaxell

Cでは、intlongではなく、常に_size_t_を使用してすべての長さの値を格納することをお勧めします。 intlongを使用すると、整数の符号付き/符号なしのバグにさらされます。参照: INT01-C from CERT C Secure Coding Standard から。

ただし、この場合は、PHPネイティブコードインターフェイスが機能する方法により、複雑になります。

  • zend_parse_parameters()を使用して整数を読み取る場合、PHPはそれをlongに格納することを想定しています。したがって、zend_parse_parameters()を渡す必要がありますlongへのポインター、次にlongを_size_t_に慎重に変換:数値が負ではなく、_size_t_(_l >= 0 && l < SIZE_MAX_)、次に_size_t_にキャストし、その後_size_t_を使用します。

  • zend_parse_parameters()を使用して文字列を読み取るとき、PHPは、文字列を格納するためのバッファと文字列の長さを格納するためのintへのポインタを渡すことを期待します。したがって、期待どおりのzend_parse_parameters()を渡す必要があります。次に、intを_size_t_に慎重に変換します。長さが負ではなく、範囲内にあることを確認してください_size_t_(_len >= 0 && len < SIZE_MAX_)の場合は、それを_size_t_にキャストし、その後_size_t_を使用します。

    同様に、Z_STRLEN_PP()intを返すので(おそらくそう思います)、おそらく同じことをしたいと思うでしょう。

longmemcpy()に長さとして渡すことに注意してください。確かにわかりませんが、そのコードは私にはかなり疑わしい臭いがあり、整数のキャスト/切り捨てのバグのリスクが高いです。)

_buffer = php_base64_encode((unsigned char*) str, str_len, NULL);
for (pos = 0; pos < out_len; pos++) {
    if (buffer[pos] == '+') {
         ...
    } else {
         ret[pos] = buffer[pos];
_

bufferの終わりを過ぎて私に読めるように見えます。 posbufferの長さよりも小さいことを確認するには、追加のチェックが必要だと思います。

4
D.W.

とても素敵なイニシアチブ!


クイックコメント
(RFCについては後で詳しく説明します。)

「塩はシステム内で一意である必要があるだけ」は誤った仮定です。また読んでください: このソルトに関する答え

BCryptアルゴリズムに手動でソルトを提供する必要があるかわかりません。私が見る限り、それは人々が不必要な間違いをするための選択肢を開くだけです。

password_make_saltは、crypt_ファミリーの関数とより関連があるようです。混乱を避けるために、代わりにcrypt_make_saltに名前を変更してください。

最初の基本的な使用例は、定数値 'usesomesillystringfor'の使用を促進します。多くの開発者は、適切な(ランダム)ソルトの代わりに、ある種の定数値を使用するミスを犯します。

Password_make_saltは可能な限り最高のランダムソースを使用し、暗号的に安全である必要があります(php_win32_get_random_bytes()の品質についてはわかりませんが、/ dev/urandomは適切です)。

user_needs_rehash.phpの例はあまり読みやすくありません。クリーンアップを使用できます。

spec_salt.phpの例は、ランダムなバイトをBCryptアルゴリズムの有効なソルト値として提供できることを示しています。これは、BCryptのカスタムbase64アルファベットの要件と矛盾します。

1
Jacco

ハッシュの観点から見ると、Bcryptの使用は最近最も推奨される手法の1つであるため、優れています。

ユーザーが別のアルゴリズム(Scryptなど、後で独自の実装を使用することもある)の使用を選択できるようにすることも、優れたオプションです。デフォルト(および専門家ではないPHP開発者の場合)では、デフォルトのBCryptを使用します。必要に応じて、ニーズに適合させることができます。

0
Cyril N.