Nodeでは、crypto.timingSafeEqual()
を使用して、タイミング攻撃に対して安全な方法で2つの文字列が等しいかどうかを確認できます。しかし、それらは同じ長さでなければならないので、あなたはそのようなことをしなければなりません:
_return stringOne.length === stringTwo.length && crypto.timingSafeEqual(Buffer.from(stringOne), Buffer.from(stringTwo))
_
このアプローチは安全ですか?文字列の長さがリークする可能性があるので、私の意見ではそうではありません!驚いたことに、PHPのhash_equals()
が同じように機能することを発見しました:
_// Source code from php-src/ext/hash/hash.c
if (Z_STRLEN_P(known_zval) != Z_STRLEN_P(user_zval)) {
RETURN_FALSE;
}
known_str = Z_STRVAL_P(known_zval);
user_str = Z_STRVAL_P(user_zval);
/* This is security sensitive code. Do not optimize this for speed. */
for (j = 0; j < Z_STRLEN_P(known_zval); j++) {
result |= known_str[j] ^ user_str[j];
}
_
このアプローチは安全だと思いますか?
この定数時間の文字列比較の実装は正しいので、攻撃者が制御する文字列と比較される文字列の長さに関する情報がリークします。
ただし、これが認証目的で文字列をチェックしている場合は、生の文字列を比較するべきではありません。まず、入力されたパスワードをデータベースから取得したソルトで(できればbcryptまたはPBKDF2などのソルトキーストレッチハッシュ関数を使用して)ハッシュし、データベースからのハッシュされたパスワードと比較します。
パスワードログインが潜在的に機能する可能性があるようなシナリオでは、比較されるハッシュの長さは常に同じです(攻撃者が入力する文字列に関係なく)。さて、長さが同じでないシナリオがあるかもしれません。たとえば、おそらく sha256crypt を使用していたかもしれませんが、ユーザーハッシュをbcryptに移行することにしました。したがって、ユーザーが入力したパスワードが長さの異なるハッシュに対して機能するかどうかを明示的に確認し、長さの異なるハッシュがすぐに失敗した場合の脆弱性はありません。 (明らかに、使用したハッシュアルゴリズムを追跡するために識別子を使用する方が理にかなっています)。または、クライアント側のコードを使用して最初にハッシュしてからハッシュを送信し、攻撃者は送信されたハッシュの長さを微調整します。それらが誤った長さを送信する場合、すぐに失敗する問題はありません(正しいハッシュの長さが秘密であるようではありません)。
次のようなコードの記述には注意が必要ですpythonコード:
def bad_constant_time_str_compare(str1, str2):
ret_val = 0
for c1, c2 in Zip(str1, str2):
ret_val |= ord(c1) ^ ord(c2)
return ret_val == 0
Zip の仕組み(最短の文字列の長さで停止)のためにbad_constant_time_str_compare('hello', 'h')
がTrue
を返すことがわかるまで、どのらしい機能するはずです。
あなたは言うことでこれを修正することができます:
def better_constant_time_str_compare(str1, str2):
ret_val = 0
for c1, c2 in Zip(str1, str2):
ret_val |= ord(c1) ^ ord(c2)
return ret_val == 0 and len(str1) == len(str2)
しかし、それについて考えても、文字列の長さをリークするタイミング攻撃に対しては依然として脆弱です。
最後に、一定時間の文字列比較では、原則として、比較される2つの文字列の長さに関する情報がリークすることを認識してください。実行時間は、実行される比較の数に比例します。これをマスクすることもできます(ランダムな待機時間を追加するなど)が、原則として、比較される文字列の長さを知ることによって、安全なシステムが実際に弱くなることはありません。