【问题标题】:PHP Seeded, Deterministic, Cryptographically Secure PRNG (PseudoRandom Number Generator). Is it possible?PHP 种子、确定性、加密安全 PRNG(伪随机数生成器)。可能吗?
【发布时间】:2016-02-06 19:31:21
【问题描述】:

我需要在 PHP 中创建一个可证明公平(确定性和种子)的密码安全 (CS) 随机数生成器。我们正在运行 PHP 5,而 PHP 7 现在并不是一个真正的选择。但是,我找到了 PHP 7 的新 CS 函数的 polyfill,因此我实现了该解决方案 (https://github.com/paragonie/random_compat)。

我认为可以使用 srand() 来播种 random_int(),但现在我不确定是否是这种情况。甚至可以播种 CSPRNG 吗?如果可以播种,输出是否是确定性的(相同的随机结果,给定相同的种子)?

这是我的代码:

require_once($_SERVER['DOCUMENT_ROOT']."/lib/assets/random_compat/lib/random.php");

$seed_a = 8138707157292429635;
$seed_b = 'JuxJ1XLnBKk7gPASR80hJfq5Ey8QWEIc8Bt';

class CSPRNG{
    private static $RNGseed = 0;

    public function generate_seed_a(){
        return random_int(0, PHP_INT_MAX);
    }

    public function generate_seed_b($length = 35){
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $randomString = '';
        for($i = 0; $i < $length; $i++){
            $randomString .= $characters[random_int(0, strlen($characters) - 1)];
        }
        return $randomString;
    }

    public function seed($s = 0) {
        if($s == 0){
            $this->RNGseed = $this->generate_seed_a();
        }else{
            $this->RNGseed = $s;
        }
        srand($this->RNGseed);
    }

    public function generate_random_integer($min=0, $max=PHP_INT_MAX, $pad_zeros = true){
        if($this->RNGseed == 0){
            $this->seed();
        }
        $rnd_num = random_int($min, $max);
        if($pad_zeros == true){
            $num_digits = strlen((string)$max);
            $format_str = "%0".$num_digits."d";
            return sprintf($format_str, $rnd_num);
        }else{
            return $rnd_num;
        }
    }

    public function drawing_numbers($seed_a, $num_of_balls = 6){
        $this->seed($seed_a);
        $draw_numbers = array();
        for($i = 0; $i < $num_of_balls; $i++) {
            $number = ($this->generate_random_integer(1, 49));
            if(in_array($number, $draw_numbers)){
                $i = $i-1;
            }else{
                array_push($draw_numbers, $number);
            }
        }
        sort($draw_numbers);
        return $draw_numbers;
    }
}

$CSPRNG= new CSPRNG();

echo '<p>Seed A: '.$seed_a.'</p>';
echo '<p>Seed B: '.$seed_b.'</p>';
$hash = hash('sha1', $seed_a.$seed_b);
echo '<p>Hash: '.$hash.'</p>';

$drawNumbers = $CSPRNG->drawing_numbers($seed_a);
$draw_str = implode("-", $drawNumbers);
echo "<br>Drawing: $draw_str<br>";

运行此代码时,绘图 ($draw_str) 应该在每次运行时都相同,但事实并非如此。

为了证明抽奖是公平的,在中奖号码被抽出并显示之前,先选择一粒种子(种子 A)。还会生成另一个随机数(种子 B)。种子 B 用作盐并与种子 A 结合,结果被散列。该散列在绘图之前显示给用户。他们还将提供源代码,以便在选择中奖号码时显示两个种子。他们可以验证哈希是否匹配,并且一切都被公平地完成了。

【问题讨论】:

  • 你的意思是像github.com/paragonie/seedspring
  • 是的,但这需要 PHP 7。不是一个选项。
  • 我可以轻松调整 composer.json 以要求 ^5.6|^7.0 和 random_compat :\

标签: php security random cryptography numbers


【解决方案1】:

黄昏问:

你打算如何证明种子是公平选择的?可疑用户很容易声称您选择的种子会给特定用户带来有利的结果,或者您提前向特定用户透露了种子。

在您研究解决方案之前,您要解决的问题到底是什么?您的威胁模型是什么?


听起来你想要SeedSpring(0.3.0 版支持PHP 5.6)。

$prng = new \ParagonIE\SeedSpring\SeedSpring('JuxJ1XLnBKk7gPAS');
$byte = $prng->getBytes(16);
\var_dump(bin2hex($byte));

这应该总是返回:

string(32) "76482c186f7c5d1cb3f895e044e3c649"

这些数字应该是公正的,但由于它基于预先共享的种子,因此严格定义而言,它不是加密安全的。

请记住,SeedSpring 是作为玩具实现/概念验证而不是官方 Paragon Initiative Enterprises 开源安全解决方案而创建的,因此请随时对其进行分叉和调整以适合您的目的。 (我怀疑我们的分支是否会达到“稳定的 1.0.0 版本”)。

(另外,如果您要接受/奖励这些答案中的任何一个,Aaron Toponce 的答案更正确。使用 ECB 模式加密 nonce 比使用 AES 加密长 NUL 字节流更高效- CTR,获得大致相同的安全优势。这是 ECB 模式可以使用的极少数情况之一。)

【讨论】:

  • 看看我的编辑,看看我们如何证明绘图是公平的。
【解决方案2】:

首先,您不应该实现自己的用户空间 CSPRNG。您安装了 PHP 5 的操作系统已经发布了 CSPRNG,并且您应该使用它来获得所有随机性,除非您知道可以使用它,或者性能是一个问题。您应该使用random_int()random_bytes()openssl_random_pseudo_bytes()

但是,如果您必须实现用户空间 CSPRNG,那么这可以通过简单地使用 AES 库(例如:libsodium)并加密计数器来完成。伪代码是:

Uint-128 n = 0;
while true:
    output = AES-ECB(key, n);
    n++;

在这种情况下,它们的 AES 密钥需要足够的熵来抵御复杂的攻击,否则您的用户空间 CSPRNG 的安全性当然会崩溃。密钥可以是用户提供的密码的bcrypt()

如果表示为 128 位无符号整数的计数器始终是唯一的,那么每次生成器使用新计数器“播种”时,您将始终获得唯一的输出。如果它使用以前使用的计数器播种,但使用不同的键,那么输出也将不同。最好的情况是每次调用生成器时都会改变键和改变计数器。

您可能想在计数器中使用高精度时间戳,例如使用微秒精度。这很好,除非您冒着某人或某物操纵系统时钟的风险。因此,如果可以操纵时钟,那么 CSPRNG 生成器可能会受到损害。最好每次调用生成器时都提供一个新密钥,然后使用 128 位零开始加密。

另外,请注意我们正在使用带有 AES 的 ECB 模式。不要惊慌失措。欧洲央行在维护明文提供的密文结构方面存在问题。一般来说,您不应该使用 ECB 模式。但是,对于 128 位数据,您只会加密单个 ECB 块,因此不会泄露结构化数据。对于用户空间 CSPRNG,ECB 优于 CTR,因为您不必跟踪密钥、计数器对象和要加密的数据。只需要一个密钥和数据。只要确保您加密的数据永远不会超过 128 位,而且您永远不需要超过 1 个块。

CSPRNG 甚至可以播种吗?

是的,并且应该始终播种。如果您查看您的 GNU/Linux 操作系统,您可能会注意到 /var/lib/urandom/random-seed 中的一个文件。当操作系统关闭时,它会从 CSPRNG 创建该文件。在下次启动时,此文件用于为内核空间 CSPRNG 播种,以防止重复使用生成器的先前状态。每次关机时,该文件都应该更改。

如果可以播种,输出是否是确定性的(相同的随机结果,给定相同的种子)?

是的。提供相同的种子、密钥等,输出是确定性的,因此输出将是相同的。如果您的变量之一发生变化,则输出将有所不同。这就是为什么每次调用生成器时都应该重新输入密钥的原因。

【讨论】:

  • 你完全没有抓住重点。此代码用于抽奖号码。唯一真正重要的是,在给定选定种子的情况下,输出是相同的(以证明绘图是公平的)。如果可能的话,我需要知道应该怎么做。我已经在使用 random_int() polyfill(它在 PHP5 中本机不可用)。我相当确定它在幕后使用 openssl_random_pseudo_bytes() 但我没有检查。这无关紧要。只要 PRNG 是 CS,安全性并不重要。
  • @compcentral 你打算如何证明种子是公平选择的?可疑用户很容易声称您选择的种子会给特定用户带来有利的结果,或者您提前向特定用户透露了种子。
  • 公平彩票的加密协议是公平的,如果至少有一个参与者是诚实的。
  • @compcentral 不,在你的问题中,你有没有提到这是为了抽奖。你所说的基本上是:我想要一个用户空间 CSPRNG,它可以被播种,并且输出是确定性的吗?最后,这有什么意义:“只要 PRNG 是 CS,安全性并不重要”。嗯?如果安全性不重要,那么您就不需要 CSPRNG。我不确定您是否了解您要解决的问题以及您的对手是谁。
  • 查看我的编辑,了解我们如何证明绘图是公平的。我们需要它成为 CS 的唯一原因是游戏审查委员会需要它。老实说,我认为没有理由提出这个要求。只要 PRNG 可以选择一个数字,使得未来的结果无法通过分析过去的结果来确定,我们应该是好的,但它必须是 CS 才能满足它们。
猜你喜欢
  • 2015-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-20
  • 1970-01-01
  • 2011-07-02
  • 1970-01-01
  • 2012-08-19
相关资源
最近更新 更多