Nonces 是一个麻烦的问题。
不要小看这个问题,几个 CAESAR 参赛者之一的动机就是设计一种基于流密码的认证加密方案,该方案能够抵御 Nonce 的重用攻击。(例如,如果在 AES-CTR 中重用 Nonce,则会破坏消息的保密性,以至于一个一年级的编程学生都可以解密它。)
在 Nonce 的使用中,有三种主要的思路:
- 在对称密钥密码学中:使用递增的计数器,同时注意不要重复使用它。(这也意味着为发送方和接收方使用单独的计数器。)这需要有状态的编程(即在某处存储 Nonce,以便每个请求不从
1
开始)。
- 有状态的随机 Nonce。生成一个随机 Nonce,然后记住它以供以后验证。这是打败 CSRF 攻击的策略,听起来更接近所需的内容。
- 大型无状态随机 Nonce。给定一个安全的随机数生成器,您几乎可以保证在您的生命周期内不会重复使用 Nonce。这是 NaCl 用于加密的策略。
所以考虑到这一点,需要问的主要问题是:
- 以上哪种思想流派与你试图解决的问题最相关?
- 你如何生成nonce?
- 你如何验证nonce?
生成Nonce
对于任意随机nonce,回答第二个问题的方法是使用CSPRNG。对于PHP项目,这意味着使用以下之一:
这两者在道德上是等价的:
$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");
}
unset($_SESSION['nonce'][$found]);
请随意使用数据库或memcached查找等替换array_search()
。
无状态(危险地带)
这是一个难以解决的问题:您需要某种方式来防止重放攻击,但每个HTTP请求后,您的服务器都会完全遗忘。
唯一明智的解决方案是通过对过期日期/时间进行认证来最小化重放攻击的有用性。例如:
$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
);
$decoded = base64_decode($input);
if ($decoded === false) {
throw new Exception("Encoding error");
}
$mac = mb_substr($decoded, 0, 32, '8bit');
$message = mb_substr($decoded, 32, null, '8bit');
$calc = hash_hmac('sha256', $message, $authenticationKey, true);
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;
一个细心的观察者会注意到,这基本上是
JSON Web Tokens的一个不符合标准的变种。