Secure random number generation in PHP

38,537

Solution 1

You can also consider using OpenSSL openssl_random_pseudo_bytes, it's available since PHP 5.3.

 string openssl_random_pseudo_bytes ( int $length [, bool &$crypto_strong ] )

Generates a string of pseudo-random bytes, with the number of bytes determined by the length parameter. It also indicates if a cryptographically strong algorithm was used to produce the pseudo-random bytes, and does this via the optional crypto_strong parameter. It's rare for this to be FALSE, but some systems may be broken or old.

http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php

Since PHP 7 there is also random_bytes function available

string random_bytes ( int $length )

http://php.net/manual/en/function.random-bytes.php

Solution 2

I strongly recommend targeting /dev/urandom on unix systems or the crypto-api on the windows platform as an entropy source for passwords.

I can't stress enough the importance of realizing hashes are NOT magical entropy increasing devices. Misusing them in this manner is no more secure than using the seed and rand() data before it had been hashed and I'm sure you recognize that is not a good idea. The seed cancels out (deterministic mt_rand()) and so there is no point at all in even including it.

People think they are being smart and clever and the result of their labor are fragile systems and devices which put the security of their systems and the security of other systems (via poor advice) in unecessary jeopardy.

Two wrongs don't make a right. A system is only as strong as its weakest part. This is not a license or excuse to accept making even more of it insecure.


Here is some PHP code to obtain a secure random 128-bit string, from this comment at php.net by Mark Seecof:

"If you need some pseudorandom bits for security or cryptographic purposes (e.g.g., random IV for block cipher, random salt for password hash) mt_rand() is a poor source. On most Unix/Linux and/or MS-Windows platforms you can get a better grade of pseudorandom bits from the OS or system library, like this:

<?php
// get 128 pseudorandom bits in a string of 16 bytes

$pr_bits = '';

// Unix/Linux platform?
$fp = @fopen('/dev/urandom','rb');
if ($fp !== FALSE) {
    $pr_bits .= @fread($fp,16);
    @fclose($fp);
}

// MS-Windows platform?
if (@class_exists('COM')) {
    // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx
    try {
        $CAPI_Util = new COM('CAPICOM.Utilities.1');
        $pr_bits .= $CAPI_Util->GetRandom(16,0);

        // if we ask for binary data PHP munges it, so we
        // request base64 return value.  We squeeze out the
        // redundancy and useless ==CRLF by hashing...
        if ($pr_bits) { $pr_bits = md5($pr_bits,TRUE); }
    } catch (Exception $ex) {
        // echo 'Exception: ' . $ex->getMessage();
    }
}

if (strlen($pr_bits) < 16) {
    // do something to warn system owner that
    // pseudorandom generator is missing
}
?>

NB: it is generally safe to leave both the attempt to read /dev/urandom and the attempt to access CAPICOM in your code, though each will fail silently on the other's platform. Leave them both there so your code will be more portable."

Solution 3

PHP ships with a new set of CSPRNG functions (random_bytes() and random_int()). It's trivial to turn the latter function into a string generator function:

<?php
/**
 * Generate a random string, using a cryptographically secure 
 * pseudorandom number generator (random_int)
 * 
 * For PHP 7, random_int is a PHP core function
 * For PHP 5.x, depends on https://github.com/paragonie/random_compat
 * 
 * @param int $length      How many characters do we want?
 * @param string $keyspace A string of all possible characters
 *                         to select from
 * @return string
 */
function random_str(
    $length,
    $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
) {
    $str = '';
    $max = mb_strlen($keyspace, '8bit') - 1;
    if ($max < 1) {
        throw new Exception('$keyspace must be at least two characters long');
    }
    for ($i = 0; $i < $length; ++$i) {
        $str .= $keyspace[random_int(0, $max)];
    }
    return $str;
}

If you need to use this in a PHP 5 project, feel free to grab a copy of random_compat, which is a polyfill for these functions.

Share:
38,537
rwallace
Author by

rwallace

Updated on December 08, 2020

Comments

  • rwallace
    rwallace over 3 years

    Use case: the "I forgot my password" button. We can't find the user's original password because it's stored in hashed form, so the only thing to do is generate a new random password and e-mail it to him. This requires cryptographically unpredictable random numbers, for which mt_rand is not good enough, and in general we can't assume a hosting service will provide access to the operating system to install a cryptographic random number module etc. so I'm looking for a way to generate secure random numbers in PHP itself.

    The solution I've come up with so far involves storing an initial seed, then for each call,

    result = seed
    seed = sha512(seed . mt_rand())
    

    This is based on the security of the sha512 hash function (the mt_rand call is just to make life a little more difficult for an adversary who obtains a copy of the database).

    Am I missing something, or are there better known solutions?

  • Adam
    Adam about 12 years
    Here's some code to do this: php.net/manual/en/function.mt-rand.php#83655
  • Ilmari Karonen
    Ilmari Karonen about 12 years
    @Tom, Einstein: Given that this question seems to be the first result on Google for "php secure random", I felt it would be a good idea to quote the code sample verbatim, just in case the link breaks some day.
  • Herbert
    Herbert almost 12 years
    UPDATE: From Windows Platform SDK Redistributable: CAPICOM - "CAPICOM is excluded from the Windows SDK beginning with the Windows SDK for Windows 7, and although the distributable will run on 32-bit versions of Windows 7 and Windows Server 2008 R2 operating systems, its use is not recommended. For more information, see Alternatives to Using CAPICOM"
  • Pacerier
    Pacerier over 9 years
    @Einstein, Why not simply use openssl_random_pseudo_bytes?
  • David Meister
    David Meister over 7 years
    This is not secure, as per paragonie.com/blog/2015/07/…
  • Petah
    Petah over 3 years
    @DavidMeister that literally says "Quick Answer: If you're developing a web application and your project needs a simple and safe solution for generating random integers or strings, just use random_bytes()"