如何创建和使用nonce

86

我正在运营一个网站,有一个评分系统,按照玩游戏的次数给您积分。

它使用哈希算法来证明http请求的完整性,以便用户无法更改任何内容。但正如我担心的那样,有人发现他们不需要更改它,只需要获得高分并复制http请求、标头等所有内容。

以前,我被禁止防范这种攻击,因为人们认为这种攻击不太可能发生。但是,既然已经发生了,我现在可以采取措施。http请求起源于Flash游戏,然后由php进行验证,并将其输入到数据库中。

我相信一次性密码(非ces)可以解决问题,但我不确定如何实现它们。设置nonce系统的常见且安全的方法是什么?


1
请注意,Flash游戏在客户端上执行的任何操作都可以被具有反编译器/数据包嗅探器和足够时间的人复制。因此,您添加的任何保护都可能会被攻破。 - cdhowie
我关心的是增加虚假操纵所需的时间。是的,他们可以反编译并替换它,但哈希算法并不是秘密,只是因为有一个秘密盐才能保护,如果他们聪明的话,可以用彩虹表来解决。 - Malfist
14
这就是禁言的原因所在。 - tplaner
3
其实可以记录游戏过程并在服务器上重新播放,然后获取从重播中获得的分数。简而言之,这是一种编码上的麻烦。 - Maurycy
你可以使用哪些值来创建nonce?是否有登录系统?能否更好地解释一下评分? - davidosomething
显示剩余3条评论
5个回答

65

实际上,这很容易... 有一些库可以帮你完成:

  1. PHP Nonce Library
  2. OpenID Nonce Library

或者如果你想自己编写,也很简单。以WikiPedia页面为起点,使用伪代码:

在服务器端,需要两个可调用的客户端函数。

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;
}

同时,在客户端:

sendData($data) {
    $nonce = getNonceFromServer();
    $cnonce = hash('sha512', makeRandomString());
    $hash = hash('sha512', $nonce . $cnonce . $data);
    $args = array('data' => $data, 'cnonce' => $cnonce, 'hash' => $hash);
    sendDataToClient($args);
}

函数makeRandomString实际上只需要返回一个随机数或字符串。随机性越好,安全性就越高...同时请注意,由于它直接被输入到哈希函数中,所以从请求到请求,实现细节并不重要。客户端的版本和服务器的版本不需要匹配。事实上,唯一需要完全匹配的是在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;
}

2
很好的答案,但是通过生成一个 nonce 然后通过 http(即明文传输)发送并不能防止重放攻击。这是“你能相信客户端吗”的古老问题,答案通常是“不能”。除非你有服务器端会话逻辑,否则这很难实现。 - zebrabox
6
@zebrabox,Nonce可以防止重放攻击。Nonce是“只使用一次的数字”,因此您只需维护先前使用过的Nonce列表,并拒绝任何尝试多次使用Nonce的请求。 - Malfist
2
因为链接库FT-Nonce在生成唯一密钥时非常不安全,所以被给予了负评。 - Corey Ballou
创建$id的逻辑是什么?如果我们使用会话,需要将会话ID传递给客户端,客户端将会话ID发送回服务器以识别正在使用的随机数? - TomSawyer

35

Nonces 是一个麻烦的问题。

不要小看这个问题,几个 CAESAR 参赛者之一的动机就是设计一种基于流密码的认证加密方案,该方案能够抵御 Nonce 的重用攻击。(例如,如果在 AES-CTR 中重用 Nonce,则会破坏消息的保密性,以至于一个一年级的编程学生都可以解密它。)

在 Nonce 的使用中,有三种主要的思路:

  1. 在对称密钥密码学中:使用递增的计数器,同时注意不要重复使用它。(这也意味着为发送方和接收方使用单独的计数器。)这需要有状态的编程(即在某处存储 Nonce,以便每个请求不从 1 开始)。
  2. 有状态的随机 Nonce。生成一个随机 Nonce,然后记住它以供以后验证。这是打败 CSRF 攻击的策略,听起来更接近所需的内容。
  3. 大型无状态随机 Nonce。给定一个安全的随机数生成器,您几乎可以保证在您的生命周期内不会重复使用 Nonce。这是 NaCl 用于加密的策略。
所以考虑到这一点,需要问的主要问题是:
  1. 以上哪种思想流派与你试图解决的问题最相关?
  2. 你如何生成nonce?
  3. 你如何验证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");
}
// Yay, now delete it.
unset($_SESSION['nonce'][$found]);

请随意使用数据库或memcached查找等替换array_search()

无状态(危险地带)

这是一个难以解决的问题:您需要某种方式来防止重放攻击,但每个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)

一个细心的观察者会注意到,这基本上是JSON Web Tokens的一个不符合标准的变种。

2
你不能通过将nonce写入数据库并在“兑换”后从数据库中删除来防止重放攻击吗?因此,采用“有状态”的方法,将$_SESSION ['nonces']替换为一个nonce表,该表将nonce与用户名(以及可能的超时)相关联。 - Stephen R
1
这就是我们所做的,但加入了一个过期列,以便我们可以限制生命周期并清除被遗弃的SSO尝试。 - Majestic12

2

一种选择(我在评论中提到过)是录制游戏过程并在安全环境中回放。

另一件事是随机或在某些指定时间记录一些看似无害的数据,稍后可以在服务器上验证它(例如,突然直播从1%变为100%,或者分数从1变为1000,这表明作弊)。有了足够的数据,作弊者可能根本无法尝试伪造它。然后当然要实施严厉的禁令:)。


2

这个非常简单的随机数每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;
}   

通过检查先前的密码随机数,用户只有在最坏情况下等待了16.6分钟或最好情况下等待了33分钟才会受到干扰。将$q=-4$设置为至少给用户2.7小时。

原始答案:Original Answer

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;
     }
}

$seed可以是在过程中使用的任何函数调用或用户名等。

最初的回答:

$seed可以是在处理过程中使用的任何函数调用、用户名等。


0

防止作弊是不可能的,你只能让它变得更困难。

如果有人在这里寻找一个PHP Nonce Library:我建议不要使用ircmaxwell给出的第一个

网站上的第一条评论描述了设计缺陷:

Nonce仅适用于某个特定的时间窗口,即用户接近该窗口结束时,提交表单的时间会越来越短,可能不到一秒

如果你正在寻找一种生成具有明确定义生命周期的Nonces的方法,请看看NonceUtil-PHP

免责声明:我是NonceUtil-PHP的作者


请确认一下,这个实用程序是一年前编写的,是否仍然是一个好选择? - Nathan Arthur
5
如果你查看作者,它是Timo本人。他的NonceUtil.php使用了sha-1,没有检查先前的nonce,并且有一个硬编码盐的字符串...在ircmaxwell的答案中列出的第一个库使用md5,第二个库使用简单的rand来创建nonce。即没有使用sha256/sha512。然而,如果nonce的目的只是为了有一个不可重复的字符串,那么你只需要一个好的随机生成器(例如:openssl_random_pseudo_bytes),然后将生成的nonce存储起来以便在使用之前进行检查。 - Armfoot
2
我不建议目前实现的NonceUtil。 - Scott Arciszewski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接