如何创建和使用nonces ?

时间:2021-03-23 18:24:42

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?

我很确定nonces将解决这个问题,但我不确定如何实现它们。建立nonce系统的一种常见的、安全的方法是什么?

4 个解决方案

#1


54  

It's actually quite easy to do... There are some libraries out there to do it for you:

这其实很容易做到……有一些图书馆可以帮你:

  1. PHP Nonce Library
  2. PHP Nonce库
  3. OpenID Nonce Library
  4. OpenID Nonce图书馆

Or if you want to write your own, it's pretty simple. Using the WikiPedia page as 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%的位是散列中使用的散列函数('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;
}

#2


17  

Nonces are a can of worms.

No, really, one of the motivations for several CAESAR entries 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.)

不,实际上,几个凯撒条目的动机之一是设计一个经过身份验证的加密方案,最好是基于流密码,该加密方案不允许一次性重用。(例如,将nonce与AES-CTR一起使用,就会破坏消息的保密性,破坏到一年级编程专业学生能够解密的程度。)

There are three main schools of thought with nonces:

有三种主要的思想流派。

  1. 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).
  2. 在对称密钥密码学中:使用增加的计数器,同时注意不要重用它。(这也意味着对发送方和接收方使用单独的计数器。)这需要有状态编程(例如,将nonce存储在某处,以便每个请求不会从1开始)。
  3. 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.
  4. 随机目前状态。生成一个随机的nonce,然后记住它以便稍后验证。这是用来击败CSRF攻击的策略,听起来更接近这里的要求。
  5. 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 NaCl for encryption.
  6. 大型随机目前无状态。给定一个安全的随机数生成器,您几乎可以保证在您的生命周期中不会重复两次nonce。这是NaCl用于加密的策略。

So with that in mind, the main questions to ask are:

考虑到这一点,主要的问题是:

  1. Which of the above schools of thought are most relevant to the problem you are trying to solve?
  2. 以上哪一种思想与你要解决的问题最相关?
  3. How are you generating the nonce?
  4. 如何生成nonce?
  5. How are you validating the nonce?
  6. 你是如何验证这个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:

对于任何随机的nonce,问题2的答案是使用CSPRNG。对于PHP项目,这意味着:

  • random_bytes() for PHP 7+ projects
  • 用于PHP 7+项目的random_bytes()
  • paragonie/random_compat, a PHP 5 polyfill for random_bytes()
  • 一个PHP 5的多填充随机字节()
  • 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
  • ircmaxell/RandomLib是瑞士军刀的随机性实用工具,大多数处理随机性的项目(例如fir password resets)应该考虑使用它,而不是使用它们自己的

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

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.

可以用数据库或memcached查找等替换array_search()。

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标记的一个不符合标准的变体。

#3


1  

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的分数表示作弊)。有了足够的数据,作弊者可能就不可能试图作假。然后当然实施了严厉的禁令:)。

#4


0  

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

nonce在一个时间窗口中是好的,即用户越接近窗口的末端,他或她提交表单的时间就越少,可能不到一秒钟

If you are looking for a way to generate Nonces with a well-defined lifetime, have a look at NonceUtil-PHP.

如果您正在寻找一种方法来生成具有定义良好的生命周期的非ceutil - php。

#1


54  

It's actually quite easy to do... There are some libraries out there to do it for you:

这其实很容易做到……有一些图书馆可以帮你:

  1. PHP Nonce Library
  2. PHP Nonce库
  3. OpenID Nonce Library
  4. OpenID Nonce图书馆

Or if you want to write your own, it's pretty simple. Using the WikiPedia page as 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%的位是散列中使用的散列函数('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;
}

#2


17  

Nonces are a can of worms.

No, really, one of the motivations for several CAESAR entries 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.)

不,实际上,几个凯撒条目的动机之一是设计一个经过身份验证的加密方案,最好是基于流密码,该加密方案不允许一次性重用。(例如,将nonce与AES-CTR一起使用,就会破坏消息的保密性,破坏到一年级编程专业学生能够解密的程度。)

There are three main schools of thought with nonces:

有三种主要的思想流派。

  1. 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).
  2. 在对称密钥密码学中:使用增加的计数器,同时注意不要重用它。(这也意味着对发送方和接收方使用单独的计数器。)这需要有状态编程(例如,将nonce存储在某处,以便每个请求不会从1开始)。
  3. 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.
  4. 随机目前状态。生成一个随机的nonce,然后记住它以便稍后验证。这是用来击败CSRF攻击的策略,听起来更接近这里的要求。
  5. 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 NaCl for encryption.
  6. 大型随机目前无状态。给定一个安全的随机数生成器,您几乎可以保证在您的生命周期中不会重复两次nonce。这是NaCl用于加密的策略。

So with that in mind, the main questions to ask are:

考虑到这一点,主要的问题是:

  1. Which of the above schools of thought are most relevant to the problem you are trying to solve?
  2. 以上哪一种思想与你要解决的问题最相关?
  3. How are you generating the nonce?
  4. 如何生成nonce?
  5. How are you validating the nonce?
  6. 你是如何验证这个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:

对于任何随机的nonce,问题2的答案是使用CSPRNG。对于PHP项目,这意味着:

  • random_bytes() for PHP 7+ projects
  • 用于PHP 7+项目的random_bytes()
  • paragonie/random_compat, a PHP 5 polyfill for random_bytes()
  • 一个PHP 5的多填充随机字节()
  • 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
  • ircmaxell/RandomLib是瑞士军刀的随机性实用工具,大多数处理随机性的项目(例如fir password resets)应该考虑使用它,而不是使用它们自己的

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

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.

可以用数据库或memcached查找等替换array_search()。

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标记的一个不符合标准的变体。

#3


1  

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的分数表示作弊)。有了足够的数据,作弊者可能就不可能试图作假。然后当然实施了严厉的禁令:)。

#4


0  

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

nonce在一个时间窗口中是好的,即用户越接近窗口的末端,他或她提交表单的时间就越少,可能不到一秒钟

If you are looking for a way to generate Nonces with a well-defined lifetime, have a look at NonceUtil-PHP.

如果您正在寻找一种方法来生成具有定义良好的生命周期的非ceutil - php。