ノンスはワームの缶です。
いいえ、実際、いくつかのCAESARエントリの動機の1つは、ナンスの再利用に耐性のある、できればストリーム暗号に基づく認証済み暗号化スキームを設計することでした。(たとえば、AES-CTRでナンスを再利用すると、プログラミングの1年生がメッセージを解読できる程度にメッセージの機密性が失われます。)
ノンスの3つの主要な考え方があります。
- 対称鍵暗号の場合:再利用しないように注意しながら、増加するカウンターを使用します。(これは、送信者と受信者に別々のカウンターを使用することも意味します。)これには、ステートフルプログラミングが必要です(つまり、各要求がで開始されないようにナンスをどこかに格納し
1
ます)。
- ステートフルランダムナンス。ランダムなナンスを生成し、後で検証するためにそれを記憶します。これは、CSRF攻撃を打ち負かすために使用される戦略であり、ここで求められているものに近いように聞こえます。
- 大きなステートレスランダムナンス。安全な乱数ジェネレーターがあれば、生涯でナンスを2回繰り返さないことをほぼ保証できます。これは、 NaClが暗号化に使用する戦略です。
したがって、それを念頭に置いて、尋ねるべき主な質問は次のとおりです。
- 上記の考え方のうち、あなたが解決しようとしている問題に最も関連しているのはどれですか?
- どのようにナンスを生成していますか?
- ノンスをどのように検証していますか?
ノンスの生成
ランダムナンスの質問2に対する答えは、CSPRNGを使用することです。PHPプロジェクトの場合、これは次のいずれかを意味します。
これら2つは道徳的に同等です:
$factory = new RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$_SESSION['nonce'] [] = $generator->generate(32);
と
$_SESSION['nonce'] []= random_bytes(32);
ノンスの検証
ステートフル
ステートフルナンスは簡単で推奨されます:
$found = array_search($nonce, $_SESSION['nonces']);
if (!$found) {
throw new Exception("Nonce not found! Handle this or the app crashes");
}
// Yay, now delete it.
unset($_SESSION['nonce'][$found]);
array_search()
をデータベースやmemcachedルックアップなどに置き換えてください。
ステートレス(ここではドラゴン)
これは解決するのが難しい問題です。リプレイ攻撃を防ぐための何らかの方法が必要ですが、サーバーには各HTTPリクエストの後に完全な記憶喪失があります。
唯一の正しい解決策は、リプレイ攻撃の有用性を最小限に抑えるために有効期限の日時を認証することです。例えば:
// Generating a message bearing a nonce
$nonce = random_bytes(32);
$expires = new DateTime('now')
->add(new DateInterval('PT01H'));
$message = json_encode([
'nonce' => base64_encode($nonce),
'expires' => $expires->format('Y-m-d\TH:i:s')
]);
$publishThis = base64_encode(
hash_hmac('sha256', $message, $authenticationKey, true) . $message
);
// Validating a message and retrieving the nonce
$decoded = base64_decode($input);
if ($decoded === false) {
throw new Exception("Encoding error");
}
$mac = mb_substr($decoded, 0, 32, '8bit'); // stored
$message = mb_substr($decoded, 32, null, '8bit');
$calc = hash_hmac('sha256', $message, $authenticationKey, true); // calcuated
if (!hash_equals($calc, $mac)) {
throw new Exception("Invalid MAC");
}
$message = json_decode($message);
$currTime = new DateTime('NOW');
$expireTime = new DateTime($message->expires);
if ($currTime > $expireTime) {
throw new Exception("Expired token");
}
$nonce = $message->nonce; // Valid (for one hour)
注意深い観察者は、これが基本的にJSONWebトークンの非標準準拠のバリアントであることに気付くでしょう。