PHP, детерминированный, Криптографически безопасный PRNG (Генератор псевдослучайных чисел). Возможно ли это?


Мне необходимо создать доказуемо честный (детерминированный и посеянный) криптографически безопасный (CS) генератор случайных чисел в PHP. Мы используем PHP 5, а PHP 7 на самом деле сейчас не вариант. Тем не менее, я нашел заполнение для новых функций CS PHP 7, поэтому я реализовал это решение (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) должен быть одинаковым при каждом запуске, но это не так.

Чтобы доказать, что розыгрыш справедлив, семя (Семя А) выбирается до того, как будет выбран и показан выигрышный номер. Также генерируется другое случайное число (начальное число B). Семя B используется в качестве соли и объединяется с семенем A, и результат хэшируется. Этот хэш является показывается пользователю перед рисованием. Им также будет предоставлен исходный код, чтобы при выборе выигрышного номера раскрывались оба семени. Они могут убедиться, что хэш совпадает и все было сделано честно.

Author: compcentral, 2016-02-06

2 answers

Даскуафф спрашивает:

Как вы намерены доказать, что семя было выбрано справедливо? Подозрительный пользователь может легко заявить, что вы выбрали семя, которое приведет к благоприятному результату для определенных пользователей, или что вы раскрыли семя определенным пользователям заранее.

Прежде чем вы начнете исследовать решения, в чем именно заключается проблема, которую вы пытаетесь решить? Какова ваша модель угроз?


Это звучит так, как вы хотите Зачаток (версия 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").

(Кроме того, если вы собираетесь принять/присудить награду за любой из этих ответов, ответ Аарона Топонса более правильный. Шифрование nonce в режиме ECB более эффективно, чем шифрование длинного потока нулевых байтов с помощью AES-CTR, примерно с тем же преимуществом в плане безопасности. Это один из крайне редких случаев, когда режим ЕЦБ в порядке.)

 4
Author: Scott Arciszewski, 2016-02-10 02:01:13

Во-первых, вы не должны внедрять свой собственный CSPRNG в пользовательском пространстве. Операционная система, в которой у вас установлен PHP 5, уже содержит CSPRNG, и вы должны использовать ее при всей своей случайности, если только вы не знаете, что можете ее использовать, или производительность вызывает беспокойство. Вы должны использовать random_int(), random_bytes(), или openssl_random_pseudo_bytes().

Однако, если вам необходимо реализовать CSPRNG пользовательского пространства, то это можно сделать, просто используя библиотеку AES (например, libsodium) и зашифровав счетчик. Psuedocode будет быть:

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

В данном случае ключу AES требуется достаточная энтропия, чтобы противостоять сложной атаке, иначе, конечно, безопасность вашего пользовательского пространства CSPRNG рухнет. Ключом может быть bcrypt() пароля, предоставленного пользователем.

При условии, что ваш счетчик, представленный в виде 128-битного целого числа без знака, всегда уникален, вы всегда будете получать уникальный вывод каждый раз, когда генератор "заполняется" новым счетчиком. Если он заполнен ранее использованным счетчиком, но другим ключом, то результат также будет другим. Лучшим вариантом развития событий было бы изменение ключа и изменение счетчика при каждом вызове генератора.

У вас может возникнуть соблазн использовать в счетчике метку времени высокой точности, например, с точностью до микросекунды. Это нормально, за исключением того, что вы рискуете, что кто-то или что-то будет манипулировать системными часами. Таким образом, если часами можно манипулировать, то генератор CSPRNG может быть скомпрометирован. Вам лучше всего предоставлять новый ключ каждый раз, когда вы звоните в генератор и начните шифрование с 128-битного нуля.

Кроме того, обратите внимание, что мы используем режим ECB с AES. Не сходи с ума. У ЕЦБ есть проблемы с сохранением структуры в зашифрованном тексте, который предоставляет открытый текст. В общих чертах, вы не должны использовать режим ЕЦБ. Однако, имея 128-битные данные, вы будете шифровать только один блок ECB, поэтому утечки структурированных данных не будет. ECB предпочтительнее CTR для CSPRNG пользовательского пространства, так как вам не нужно отслеживать ключ, счетчик объект и данные, подлежащие шифрованию. Необходимы только ключ и данные. Просто убедитесь, что вы никогда не шифруете более 128 бит данных, и вам никогда не понадобится больше 1 блока.

Можно ли вообще посеять CSPRNG?

Да, и он всегда должен быть посеян. Если вы посмотрите на свою операционную систему GNU/Linux, вы, скорее всего, заметите файл в /var/lib/urandom/random-seed. Когда операционная система завершает работу, она создает этот файл из CSPRNG. При следующей загрузке этот файл используется для заполнения пространство ядра CSPRNG для предотвращения повторного использования предыдущего состояния генератора. При каждом завершении работы этот файл должен изменяться.

Если его можно посеять, будет ли результат детерминированным (тот же случайный результат, заданный тем же семенем)?

Да. При условии одинакового начального значения, ключа и т. Д. Результат является детерминированным, поэтому результат будет одинаковым. Если одна из ваших переменных изменится, то результат будет другим. Вот почему при каждом вызове генератора следует повторять вызов.

 5
Author: Aaron Toponce, 2016-02-09 17:12:18