如何创建和使用随机数


74

我正在运行一个网站,并且有一个计分系统,可为您提供玩游戏次数的积分。

它使用散列来证明http请求评分的完整性,因此用户无法更改任何内容,但是正如我担心的那样,有人发现他们不需要更改它,他们只需要获得高分并复制http请求,标头和所有。

以前,我被禁止防范此攻击,因为它被认为不太可能。但是,既然已经发生,我可以。http请求源自Flash游戏,然后由php验证,然后php将其输入数据库。

我很确定随机数会解决该问题,但是我不确定如何实现它们。建立随机数系统的常见且安全的方法是什么?


1
请注意,具有反编译器/数据包嗅探器且时间足够的人可以复制Flash游戏在客户端上执行的所有操作。因此,您添加的任何保护都可能会失败。
cdhowie 2010年

这是我有兴趣增加的虚假维护时间。是的,他们可以反编译并替换它,但是哈希算法不是秘密,而只能保护它,因为它有一个秘密的盐,如果他们很聪明,他们可以用彩虹表找出来。
Malfist 2010年

13
这就是禁锤被发明的原因。
tplaner 2010年

3
@cdhowie-并非如此,您可以录制一个游戏的玩法,然后在服务器上重播它,然后获取从重播中获得的分数,以简化内容。编码恐怖虽然;)。
莫里西

莫里西(Maurycy):这不会阻止您反复玩同一游戏。这也不会阻止人们实现自己的游戏记录生成器。
cdhowie 2010年

Answers:


61

实际上,这很容易做到。有一些库可以为您做:

  1. PHP Nonce库
  2. OpenID Nonce库

或者,如果您想编写自己的文件,这很简单。使用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实际上只需要返回一个随机数或字符串。随机性越好,安全性就越好。。还要注意,由于它是直接输入到哈希函数中的,因此实现细节在请求之间无关紧要。客户端的版本和服务器的版本不需要匹配。实际上,唯一需要匹配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;
}

2
好的答案是,但是生成一个随机数然后通过http(即,开放式网络)进行传输并不能阻止重放攻击。“您可以信任客户”这个古老的问题,答案总是“不”。除非您具有服务器端会话逻辑,否则这很难。
zebrabox 2010年

5
@ zebrabox,Nonces可以防止重放攻击。随机数是“仅使用一次的数字”,因此您只需维护以前使用的随机数的列表,并拒绝任何两次使用随机数的尝试。
Malfist 2011年

2
不推荐使用,因为链接库FT-Nonce在生成唯一密钥方面非常不安全。
科里·巴拉

创建的逻辑是$id什么?如果使用会话,则需要将会话ID传递给客户端,然后客户端将会话ID发送回服务器以识别正在使用的随机数?
TomSawyer

29

随机数是一罐蠕虫。

不,真的,是几家CAESAR的动机之一条目是设计一种经过验证的加密方案,最好基于流密码,这种方案可以抵抗随机数重用。(例如,使用AES-CTR重用随机数,会破坏您的消息的机密性,使编程的学生一年级可以解密该消息。)

存在三种主要的带有随机数的思想流派:

  1. 在对称密钥加密中:使用递增计数器,同时注意不要重用它。(这也意味着对发送方和接收方使用单独的计数器。)这需要状态编程(即,将随机数存储在某个位置,这样每个请求都不会在处开始1)。
  2. 有状态随机数。生成随机随机数,然后记住它以便以后进行验证。这是用来战胜CSRF攻击的策略,听起来更接近此处的要求。
  3. 大型无状态随机数。给定一个安全的随机数生成器,您几乎可以保证一生中从不重复两次随机数。这是NaCl用于加密的策略。

因此,请记住以下主要问题:

  1. 以上哪种思想流派与您要解决的问题最相关?
  2. 您如何产生随机数?
  3. 您如何验证随机数?

产生一个随机数

对于任何随机随机数,问题2的答案是使用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]);

随时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令牌的不符合标准的变体。


2
您不能通过将随机数写入数据库来防止重播攻击,并且在“赎回”时将其从数据库中删除吗?因此,采用您的“有状态”方法,并用将随机数链接到用户名(可能是超时)的随机数表替换$ _SESSION ['nonces']吗?
斯蒂芬·R

1
这就是我们要做的,但是有一个到期列,因此我们可以限制寿命并清除放弃的SSO尝试。
Majestic18年

2

一种选择(我在评论中提到)是录制游戏并在安全的环境中重播。

另一件事是随机地或在指定的时间记录一些看似无害的数据,这些数据以后可用于在服务器上对其进行验证(例如突然的直播从1%上升到100%,或从1到1000得分表示作弊) )。有了足够的数据,作弊者尝试伪造它可能是不可行的。然后当然实施重禁:)。


0

无法防止作弊。您只能使其更加困难。

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

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

随机数在一个特定的时间范围内有效,即用户越接近该窗口的末端,他或她提交表单的时间越短,可能少于一秒钟

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

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


您能否确认一年多以前创作的该实用程序仍然是一个不错的选择?
弥敦道·亚瑟

5
@NathanArthur如果您检查作者,那是Timo本人。他的NonceUtil.php使用sha-1,不检查以前的随机数,并且具有盐的硬编码字符串... ircmaxwell的答案中列出的第一个库使用md5,第二个库使用简单的rand创建随机数。即没有人使用sha256 / sha512。但是,如果随机数的目标只是拥有一个不可重复的字符串,那么您只需要一个好的随机生成器(例如:openssl_random_pseudo_bytes),然后存储生成的生成器以便在使用它们之前进行检查。
Armfoot '16

1
我不建议将NonceUtil作为当前实施的。
Scott Arciszewski

0

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

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可以是过程中使用的任何函数调用或用户名等。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.