replace rand() with openssl_random_pseudo_bytes()

2019-01-23 04:37发布

问题:

I need a replacement for PHP's rand() function that uses a cryptographically strong random number generator.

The openssl_random_pseudo_bytes() function gets you access to the strong random number generator, but it outputs its data as a byte string. Instead, I need an integer between 0 and X.

I imagine the key is to get the output of openssl_random_pseudo_bytes() into an integer, then you can do any math on it that you need to. I can think of a few "brute force" ways of converting from a byte string to an integer, but I was hoping for something ... elegant.

回答1:

Using provided suggestions, I've created a drop-in replacement for rand() using OpenSSL. I'll include it here for posterity.

The $pedantic option gives bias-free results by starting over when results won't be evenly distributed across the possible range.

function crypto_rand($min,$max,$pedantic=True) {
    $diff = $max - $min;
    if ($diff <= 0) return $min; // not so random...
    $range = $diff + 1; // because $max is inclusive
    $bits = ceil(log(($range),2));
    $bytes = ceil($bits/8.0);
    $bits_max = 1 << $bits;
    // e.g. if $range = 3000 (bin: 101110111000)
    //  +--------+--------+
    //  |....1011|10111000|
    //  +--------+--------+
    //  bits=12, bytes=2, bits_max=2^12=4096
    $num = 0;
    do {
        $num = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes))) % $bits_max;
        if ($num >= $range) {
            if ($pedantic) continue; // start over instead of accepting bias
            // else
            $num = $num % $range;  // to hell with security
        }
        break;
    } while (True);  // because goto attracts velociraptors
    return $num + $min;
}


回答2:

The manual page for openssl_random_pseudo_bytes() has an example I think you want. You can just call bin2hex() on the output of openssl_random_pseudo_bytes() to convert to a hexadecimal number, then hexdec() on that value to convert to decimal.

$rand_num = hexdec(bin2hex(openssl_random_pseudo_bytes($length, $strong)));

At that point you can do whatever math you want to get a value in the range you need. The other (cheater) option you might have is to run a system command to generate a random number - there are a few good options for random number generators for various operating systems available online.



回答3:

Heres a version of the solutions above, which doesn't use recursive function calls:

function secure_rand($min,$max) {
    $range = $max - $min + 1;
    if ($range == 0) return $min;
    $length = (int) (log($range,2) / 8) + 1;
    $max = pow(2, 8 * $length);
    $num = $max + 1; // Hackish, I know..
    while ($num > $max) {
        $num = hexdec(bin2hex(openssl_random_pseudo_bytes($length,$s)));
    }
    return ($num  % $range) + $min;
}


回答4:

Since PHP 7 is out now, the easiest way to solve this problem is to replace all instances of mt_rand with random_int.

(Assuming you've upgraded, that is.)



回答5:

well, just use hexdec on the result of openssl_random_pseudo_bytes and you will get your integer. It is as elegant as it gets :)

print hexdec('45261b8f');

>  1160125327


回答6:

The easiest way to do this (and the most secure out of all the options here) is to use CryptoLib which has a randomInt function that provides a drop-in replacement for rand.

First download CryptoLib from and stick it in your project: https://github.com/IcyApril/CryptoLib

Two, drop in this code. replace path/to/ with the directory of cryptolib.php and the min max with your minimum and maximum numbers:

<?php
  require_once('path/to/cryptoLib.php');

  $min = 1;
  $max = 5;

  $randomNum = CryptoLib::randomInt($min, $max);
?>

The CryptoLib full documentation is at: https://cryptolib.ju.je/



回答7:

function ($min,$max){
    $range = $max-$min+1;
    do{
        $result = floor($range*(hexdec(bin2hex(openssl_random_pseudo_bytes(4)))/0xffffffff));
    } while($result == $range);    
    return $result + $min;
}