Generate cryptographically secure random numbers i

2019-01-09 01:04发布

问题:

PHP's rand() function doesn't give good random numbers. So I started to use mt_rand() which is said to give better results. But how good are these results? Are there any methods to improve them again?

My idea:

function rand_best($min, $max) {
    $generated = array();
    for ($i = 0; $i < 100; $i++) {
        $generated[] = mt_rand($min, $max);
    }
    shuffle($generated);
    $position = mt_rand(0, 99);
    return $generated[$position];
}

This should give you "perfect" random numbers, shouldn't it?

回答1:

Pseudorandom number generators (PRNG) are very complex beast.

There are no real "perfect" random number generators -- in fact the best that can be done from mathematical functions are pseudorandom -- they seem random enough for most intents and purposes.

In fact, performing any additional actions from a number returned by a PRNG doesn't really increase its randomness, and in fact, the number can become less random.

So, my best advice is, don't mess around with values returned from a PRNG. Use a PRNG that is good enough for the intended use, and if it isn't, then find a PRNG that can produce better results, if necessary.

And frankly, it appears that the mt_rand function uses the Mersenne twister, which is a pretty good PRNG as it is, so it's probably going to be good enough for most casual use.

However, Mersenne Twister is not designed to be used in any security contexts. See this answer for a solution to use when you need randomness to ensure security.

Edit

There was a question in the comments why performing operations on a random number can make it less random. For example, some PRNGs can return more consistent, less random numbers in different parts of the bits -- the high-end can be more random than the low-end.

Therefore, in operations where the high-end is discarded, and the low end is returned, the value can become less random than the original value returned from the PRNG.

I can't find a good explanation at the moment, but I based that from the Java documentation for the Random.nextInt(int) method, which is designed to create a fairly random value in a specified range. That method takes into account the difference in randomness of the parts of the value, so it can return a better random number compared to more naive implementations such as rand() % range.



回答2:

Quick answer:

In a new PHP7 there is a finally a support for a cryptographically secure pseudo-random integers.

int random_int ( int $min , int $max )

There is also a polyfill for PHP5x.

Longer answer


There is no perfect random number generator, and computers use pseudorandom number generator to create sequences that looks random. The sequences look random (and pass some randomness tests) but because there is some algorithm to generate it, you can repeat algorithm with absolutely the same states and get the same result.

The same advice as with cryptography "do not invent your own cypher" can be translated to random number generators and mean that you can not just get a lot of random number generators combined together and get expect to get a better generator.


One of the subsets of random number generators is cryptographically secure random number generators:

The requirements of an ordinary PRNG are also satisfied by a cryptographically secure PRNG, but the reverse is not true. CSPRNG requirements fall into two groups: first, that they pass statistical randomness tests; and secondly, that they hold up well under serious attack, even when part of their initial or running state becomes available to an attacker

So this is pretty close to your definition of "perfect". One more time under no condition (except of learning how to do cryptography) you should try to implement one of that algorithms and use it in your system.


But luckily PHP7 has it implemented,

int random_int ( int $min , int $max )

Generates cryptographic random integers that are suitable for use where unbiased results are critical (i.e. shuffling a Poker deck).

The sources of random are as follows:

  • On Windows CryptGenRandom() is used exclusively
  • arc4random_buf() is used if it is available (generally BSD specific)
  • /dev/arandom is used where available
  • The getrandom(2) syscall (on newer Linux kernels)
  • /dev/urandom is used where none of the above is available

This makes all the previous answers obsolete (and some deprecated).



回答3:

I'm not sure that what you've done "improves" the randomness. From what I can understand you generate 100 random numbers and then randomly pick one of them.

From what I can remember from my probability course, this probably doesn't increase the randomness, as if there is an underlying bias in the generator function (mt_rand()), then it will still be reflected somehow in the output.



回答4:

In what way is mt_rand() "bad"?

For example: If it favors a certain number. Lets say mt_rand(1, 10) favours low numbers in the range, ie "1" and "2" occurs on average more than 10% each. Then your "improvement" would still suffer from the same problem.

Selecting a random number out of a faulty sequence will still be faulty.



回答5:

<?php
  function random_number(){
      return 4; // return generated number
                // guaranteed to be random
  }
  ?>

All joking aside, you're getting into a philosophical question of what is "random" or what is "best". Ideally you'd want your random numbers to have few patterns in them over the course of your procedure. Generally system time is used as the seed, but I've also used the previous random number as the seed, the previous random numberth ago as the seed. The problem is, with a powerful enough computer and full knowledge of the hardware running, and generator function, you would be able to predict the entire set of numbers generated. Thus if you had a powerful enough computer (some people put God into this category) that knew all possible variables and functions of the universe you would then be able to predict every event that happened or will happen. Most random number generators are fine on their own but if you know someone who can see the patterns, more likely they are like the guy in Beautiful Mind and you should get them checked into a clinic.

By popular demand :D



回答6:

I wrote a cronjob that gets 1000 numbers from random.org periodically (say, once an hour) and added them into a PHP array. Whenever I want random numbers in my script, I use mt_rand(0,1000) to call a number from that. A few extra microseconds of overhead, but I get truly random numbers based on natural atmospheric noise.



回答7:

It all depends what for you need that random number :) For me ShuffleBag is the best one :)



回答8:

Edit: My comment is no longer valid. Please see the following answer: https://stackoverflow.com/a/31443898/109561


I'm guessing you're worried about the distribution of mt_rand(). I have tested it and it is very level and both bounds are inclusive.

I added my test to the comments of the documentation for mt_rand() on the php manual, but it was removed by a silly moderator due to politics that are too long winded to go into here.



回答9:

If you don't like PHP's built in rand(), you probably shouldn't use their built-in shuffle() either, since it seems to be built on their rand().

I am halfway sure the "industry standard" shuffle now is the Fisher-Yates shuffle.



回答10:

There is no such thing as a "perfect" random number. No matter what subjective definition of "perfect" you have. You can only achieve pseudo-random.

I was simply trying to point you in the right direction. You asked a question about perfect random numbers, even if perfect was in quotes. And yes, you can improve randomness. You can even implement heuristic or "natural" algorithms, such ideas like "atmospheric noise" -- but still, you're not perfect, not by any means.



回答11:

use /dev/ramdom (linux device true random number generator) to seed mt_rand

<?
$rnd_dev=mcrypt_create_iv(4, MCRYPT_DEV_RANDOM); //need "apt-get install php5-mcrypt"
$seed=ord(substr($rnd_dev, 0, 1))<<24 |
      ord(substr($rnd_dev, 1, 1))<<16 |
      ord(substr($rnd_dev, 2, 1))<<8 |
      ord(substr($rnd_dev, 3, 1));
mt_srand($seed);
echo mt_rand();
?>


回答12:

I made a PHP class for generating random numbers and strings PHPRandomValue

It uses "mcrypt_create_iv(4, MCRYPT_DEV_URANDOM)" to generate random numbers and values. I made it while working on a crypto project because I needed a safe random value generator. Here's an example usage

$randomValue = new RandomValue;

$randomValue->randomNumber(): = -3880998

$randomValue->randomNumberBetween(1,10): = 2

$randomValue->randomTextString(): = CfCkKDHRgUULdGWcSqP4

$randomValue->randomTextString(10):  = LorPIxaeEY

$randomValue->randomKey(): = C7al8tX9.gqYLf2ImVt/!$NOY79T5sNCT/6Q.$!.6Gf/Q5zpa3

$randomValue->randomKey(10):  = RDV.dc6Ai/


回答13:

It is not possible to generate true random numbers, the best you can hope for is pseudo-random which is what rand() provides, your function is no closer to random then rand(). Take a look at this http://en.wikipedia.org/wiki/Random_number_generator



回答14:

Tru Random numbers

<?php
for ($i = -1; $i <= 4; $i++) {
    $bytes = openssl_random_pseudo_bytes($i, $cstrong);
    $hex   = bin2hex($bytes);

    echo "Lengths: Bytes: $i and Hex: " . strlen($hex) . PHP_EOL;
    var_dump($hex);
    var_dump($cstrong);
    echo PHP_EOL;
}
?>

and also crypto secure ;)



回答15:

Although the answer was accepted years ago, I'll re-reopen it.

Since all this randomness depends on the system time, let's mess with the system time too! The amount of time an operation takes on the computer is actually rather variable (especially if other stuff is happening on that server), so if we take advantage of that with microtime... (couldn't find any portable nanotime commands)

$a='';
for (int $i=0; $i<9001; $i++)
{
    usleep(mt_rand(1000,10000));//Also eliminates timing attacks... possibly?
    $a=hash('SHA512',$a.uniqid(mt_rand().microtime(),true));
}
echo $a;

Nominally this has 207023 bits of entropy, since you're adding another 23 bits every iteration, but there's a lot of interdependencies, so it's probably a few orders of magnitude less. Still pretty good.

Do you know of any operations on PHP that take a really random amount of time? Like... HTTP-requesting some website (other than RANDOM.org) and measuring the time it takes?



回答16:

Using random.org, you can use this:

function getToken($length, $min, $max){
    $r = explode('
',file_get_contents('http://www.random.org/integers/num='.$length.'&min='.$min.'&max='.$max.'&col=1&base=10&format=plain'));

    $string = '';
    foreach ( $r as $char ) $string.=$char;
    return $string;
}

this should give real random numbers