php 如何创建和使用 nonce
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4145531/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to create and use nonces
提问by Malfist
I am running a website, and there is a scoring system that gives you points for the number of times you play a game.
我正在运行一个网站,并且有一个评分系统,可以根据您玩游戏的次数为您提供积分。
It uses hashing to prove the integrity of http request for scoring so users cannot change anything, however as I feared might happen, someone figured out that they didn't need to change it, they just needed to get a high score, and duplicate the http request, headers and all.
它使用散列来证明用于评分的 http 请求的完整性,因此用户无法更改任何内容,但是正如我担心可能会发生的那样,有人发现他们不需要更改它,他们只需要获得高分,并复制http 请求、标头和所有内容。
Previously I'd been prohibited from protecting against this attack because it was considered unlikely. However, now that it has happened, I can. The http request originates from a flash game, and then is validated by php and php enters it into the database.
以前,我被禁止防止这种攻击,因为它被认为不太可能。然而,既然它已经发生了,我可以。http 请求源自一个 Flash 游戏,然后由 php 验证,然后 php 将其输入到数据库中。
I'm pretty sure nonces will solve the issue, but I'm not exactly sure how to implement them. What is a common, and secure way of setting up a nonce system?
我很确定随机数会解决这个问题,但我不确定如何实现它们。什么是设置 nonce 系统的常见且安全的方法?
采纳答案by ircmaxell
It's actually quite easy to do... There are some libraries out there to do it for you:
这实际上很容易做到......有一些图书馆可以为你做:
Or if you want to write your own, it's pretty simple. Using the WikiPedia pageas a jumping off point, In pseudo-code:
或者,如果您想自己编写,也很简单。使用WikiPedia 页面作为起点,在伪代码中:
On the server side, you need two client callable functions
在服务器端,您需要两个客户端可调用函数
getNonce() {
$id = Identify Request //(either by username, session, or something)
$nonce = hash('sha512', makeRandomString());
storeNonce($id, $nonce);
return $nonce to client;
}
verifyNonce($data, $cnonce, $hash) {
$id = Identify Request
$nonce = getNonce($id); // Fetch the nonce from the last request
removeNonce($id, $nonce); //Remove the nonce from being used again!
$testHash = hash('sha512',$nonce . $cnonce . $data);
return $testHash == $hash;
}
And on the client side:
而在客户端:
sendData($data) {
$nonce = getNonceFromServer();
$cnonce = hash('sha512', makeRandomString());
$hash = hash('sha512', $nonce . $cnonce . $data);
$args = array('data' => $data, 'cnonce' => $cnonce, 'hash' => $hash);
sendDataToClient($args);
}
The function makeRandomString
really just needs to return a random number or string. The better the randomness, the better the security... Also note that since it's fed right into a hash function, the implementation details don't matter from request to request. The client's version and the server's version don't need to match. In fact, the only bit that needs to match 100% is the hash function used in hash('sha512', $nonce . $cnonce . $data);
... Here's an example of a reasonably secure makeRandomString
function...
该函数makeRandomString
实际上只需要返回一个随机数或字符串。随机性越好,安全性就越好……另外请注意,由于它直接输入到散列函数中,因此请求与请求之间的实现细节无关紧要。客户端的版本和服务器的版本不需要匹配。事实上,唯一的一点是需要匹配100%是在使用的哈希函数hash('sha512', $nonce . $cnonce . $data);
...这是一个相当安全的一个例子makeRandomString
功能...
function makeRandomString($bits = 256) {
$bytes = ceil($bits / 8);
$return = '';
for ($i = 0; $i < $bytes; $i++) {
$return .= chr(mt_rand(0, 255));
}
return $return;
}
回答by Scott Arciszewski
Nonces are a can of worms.
Nonce 是一罐蠕虫。
No, really, one of the motivations for several CAESARentries was to design an authenticated encryption scheme, preferably based on a stream cipher, that is resistant to nonce reuse. (Reusing a nonce with AES-CTR, for example, destroys the confidentiality of your message to the degree a first year programming student could decrypt it.)
不,实际上,几个CAESAR条目的动机之一是设计一个经过身份验证的加密方案,最好基于流密码,可以抵抗 nonce 重用。(例如,通过 AES-CTR 重用随机数会破坏消息的机密性,达到一年级编程学生可以解密的程度。)
There are three main schools of thought with nonces:
有三种主要的关于 nonce 的思想流派:
- In symmetric-key cryptography: Use an increasing counter, while taking care to never reuse it. (This also means using a separate counter for the sender and receiver.) This requires stateful programming (i.e. storing the nonce somewhere so each request doesn't start at
1
). - Stateful random nonces. Generating a random nonce and then remembering it to validate later. This is the strategy used to defeat CSRF attacks,which sounds closer to what is being asked for here.
- Large stateless random nonces. Given a secure random number generator, you can almost guarantee to never repeat a nonce twice in your lifetime. This is the strategy used by NaClfor encryption.
- 在对称密钥加密中:使用递增计数器,同时注意不要重复使用它。(这也意味着对发送方和接收方使用单独的计数器。)这需要有状态的编程(即,将随机数存储在某处以便每个请求不会从 开始
1
)。 - 有状态的随机数。生成一个随机随机数,然后记住它以供稍后验证。这是用于击败 CSRF 攻击的策略,听起来更接近这里的要求。
- 大型无状态随机数。给定一个安全的随机数生成器,你几乎可以保证在你的一生中永远不会重复两次随机数。这是NaCl用于加密的策略。
So with that in mind, the main questions to ask are:
因此,考虑到这一点,要问的主要问题是:
- Which of the above schools of thought are most relevant to the problem you are trying to solve?
- How are you generating the nonce?
- How are you validating the nonce?
- 上述哪些思想流派与您试图解决的问题最相关?
- 你是如何生成随机数的?
- 你如何验证随机数?
Generating a Nonce
生成随机数
The answer to question 2 for any random nonce is to use a CSPRNG. For PHP projects, this means one of:
对于任何随机随机数,问题 2 的答案是使用 CSPRNG。对于 PHP 项目,这意味着以下之一:
random_bytes()
for PHP 7+ projects- paragonie/random_compat, a PHP 5 polyfill for
random_bytes()
- ircmaxell/RandomLib, which is a swiss army knife of randomness utilities that most projects that deal with randomness (e.g. fir password resets) should consider using instead of rolling their own
random_bytes()
用于 PHP 7+ 项目- paragonie/random_compat,一个 PHP 5 polyfill,用于
random_bytes()
- ircmaxell/RandomLib,它是随机性实用程序的瑞士军刀,大多数处理随机性(例如冷杉密码重置)的项目应该考虑使用而不是自己滚动
These two are morally equivalent:
这两个在道德上是等价的:
$factory = new RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$_SESSION['nonce'] [] = $generator->generate(32);
and
和
$_SESSION['nonce'] []= random_bytes(32);
Validating a Nonce
验证 Nonce
Stateful
有状态的
Stateful nonces are easy and recommended:
有状态的随机数很简单,推荐使用:
$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]);
Feel free to substitute the array_search()
with a database or memcached lookup, etc.
随意array_search()
用数据库或 memcached 查找等替换。
Stateless (here be dragons)
无国籍(这里是龙)
This is a hard problem to solve: You need some way to prevent replay attacks, but your server has total amnesia after each HTTP request.
这是一个很难解决的问题:您需要某种方法来防止重放攻击,但是您的服务器在每个 HTTP 请求之后都完全失忆了。
The only sane solution would be to authenticate an expiration date/time to minimize the usefulness of replay attacks. For example:
唯一合理的解决方案是验证过期日期/时间,以最大限度地减少重放攻击的有用性。例如:
// 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)
A careful observer will note that this is basically a non-standards-compliant variant of JSON Web Tokens.
细心的观察者会注意到,这基本上是JSON Web Tokens的不符合标准的变体。
回答by Maurycy
One option (which I mentioned in comment) is recording gameplay and replay it in secure environment.
一种选择(我在评论中提到)是记录游戏玩法并在安全环境中重播。
The other thing is to randomly, or at some specified times, record some seemingly innocent data, which later can be used to validate it on server (like suddenly live goes from 1% to 100%, or score from 1 to 1000 which indicate cheat). With enough data it might just not be feasible for cheater to try to fake it. And then of course implement heavy banning :).
另一件事是随机地,或在某些指定的时间,记录一些看似无辜的数据,这些数据稍后可以用于在服务器上对其进行验证(例如突然直播从 1% 到 100%,或从 1 到 1000 的分数表示作弊)。有了足够的数据,骗子试图伪造它可能是不可行的。然后当然要实施严厉的禁令:)。
回答by oleviolin
This very simple nonce changes every 1000 seconds (16 minutes) and can be used for avoiding XSS where you are posting data to and from the same application. (For example if you are in a single page application where you are posting data via javascript. Note that you must have access to the same seed and nonce generator from the post and the receiving side)
这个非常简单的随机数每 1000 秒(16 分钟)更改一次,可用于避免在同一个应用程序之间发布数据的 XSS。(例如,如果您在通过 javascript 发布数据的单页应用程序中。请注意,您必须能够从发布方和接收方访问相同的种子和随机数生成器)
function makeNonce($seed,$i=0){
$timestamp = time();
$q=-3;
//The epoch time stamp is truncated by $q chars,
//making the algorthim to change evry 1000 seconds
//using q=-4; will give 10000 seconds= 2 hours 46 minutes usable time
$TimeReduced=substr($timestamp,0,$q)-$i;
//the $seed is a constant string added to the string before hashing.
$string=$seed.$TimeReduced;
$hash=hash('sha1', $string, false);
return $hash;
}
But by checking for the previous nonce, the user will only be bothered if he waited more than 16.6 minutes in worst case and 33 minutes in best case. Setting $q=-4 will give the user at least 2.7 hours
但是通过检查之前的随机数,如果用户在最坏的情况下等待超过 16.6 分钟,在最好的情况下等待超过 33 分钟,他才会被打扰。设置 $q=-4 会给用户至少 2.7 小时
function checkNonce($nonce,$seed){
//Note that the previous nonce is also checked giving between
// useful interval $t: 1*$qInterval < $t < 2* $qInterval where qInterval is the time deterimined by $q:
//$q=-2: 100 seconds, $q=-3 1000 seconds, $q=-4 10000 seconds, etc.
if($nonce==$this->makeNonce($seed,0)||$nonce==$this->makeNonce($seed,1)) {
//handle data here
return true;
} else {
//reject nonce code
return false;
}
}
The $seed, could be the any function call or user name, etc. used in the process.
$seed 可以是过程中使用的任何函数调用或用户名等。
回答by Timo Stamm
It is not possible to prevent cheating. You can only make it more difficult.
不可能防止作弊。你只能让它变得更困难。
If someone came here looking for a PHP Nonce Library: I recommend not using the first one given by ircmaxwell.
如果有人来这里寻找 PHP Nonce 库:我建议不要使用ircmaxwell 给出的第一个。
The first comment on the website describes a design flaw:
网站上的第一条评论描述了一个设计缺陷:
The nonce is good for one certain time window, i.e. the nearer the user gets to the end of that windows the less time he or she has to submit the form, possibly less than one second
随机数适用于某个特定的时间窗口,即用户越接近该窗口的末尾,他或她提交表单的时间就越少,可能不到一秒
If you are looking for a way to generate Nonces with a well-defined lifetime, have a look at NonceUtil-PHP.
如果您正在寻找一种方法来生成具有明确定义的生命周期的Nonce,请查看NonceUtil-PHP。
Disclaimer: I am the author of NonceUtil-PHP
免责声明:我是 NonceUtil-PHP 的作者