Paragon Initiative Enterprises Blog

The latest information from the team that develops cryptographically secure PHP software.

How to Securely Generate Random Strings and Integers in PHP

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() (PHP 7 only, or available through our random_compat library). Alternatively, Anthony Ferrara's RandomLib is a good choice.

<?php    
$factory = new \RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$password = $generator->generateString(26, 'abcdefghijklmnopqrstuvwxyz234567');

If you're using libsodium (which we highly recommend), you can use Sodium::randombytes_uniform() like so:

function random_str($length, $keyspace = 'abcdefghijklmnopqrstuvwxyz234567')
{
    $str = '';
    $keysize = strlen($keyspace);
    for ($i = 0; $i < $length; ++$i) {
        $str .= $keyspace[\Sodium\randombytes_uniform($keysize)];
    }
    return $str;
}

Finally, the follow-up blog post about common uses for CSPRNGs contains some example functions.

What's the Big Deal about Randomness?

Generating useful random data is a fairly common task for a developer to implement, but also one that developers rarely get right. When it comes to security-sensitive information, such as generating a random password for one of your users, getting this right can make/break your application.

There are two main types of random number generators used in modern web applications:

  1. Pseudo-Random Number Generators, like PHP's rand(), mt_rand(), uniqid(), and lcg_value()
  2. Cryptographically Secure Pseudo-Random Number Generators, like /dev/urandom and CryptGenRandom

In other words, random number generators are either weak or strong.

For technical/semantic reasons (digital computers are deterministic and therefore incapable of producing true randomness without specialized hardware), all software random number generators generate "pseudo-random" values rather than simply "random" values. Unless you have a good reason to care about this distinction, you can treat the terms as interchangeable.

It's generally not okay to use a weak random number generator unless both of the following two conditions are met:

  1. The security of your application does not depend in any way on the value you generate being unpredictable.
  2. There is no requirement for each value to be unique (up to a reasonable probability).

Put another way, if you're doing one of the following, you should be using a cryptographically secure pseudo-random number generator:

  • Creating random passwords for new user accounts
  • Generating encryption keys
  • Generating IVs for encrypting data in CBC mode
  • Generating a nonce for encrypting data with a stream cipher (or a block cipher in CTR mode)
  • Anything that can affect the security of your application in any way

Weak Random Number Generators and Their Consequences

Relying on a weak random number generator in a security context (e.g. generating a password for a new user) is one of the most prevalent (though often non-exploitable) issues that Paragon Initiative Enterprises routinely discovers in PHP applications. This issue even found its way into an earlier version of Facebook's PHP SDK. (Disclaimer: Our Chief Development Officer identified and fixed this issue before Paragon Initiative Enterprises was formed.)

The weak PRNGs (e.g. rand(), mt_rand(), and lcg_value()) and all of the "shuffling" functions (e.g. str_shuffle(), shuffle(), array_rand()) suffer from the same problem: They are seeded by a 32-bit integer, which means they can only produce up to 4 billion possible values after being seeded. This is a best case upper limit, assuming there are no flaws in their algorithms that reduce this number further.

Four billion may seem like a large number, but you can fit a list of every possible output on an inexpensive USB drive, and it will take a resourceful attacker only a few minutes to generate such a list from your algorithm.

Furthermore, with weak PRNGs, attackers can often recover the seed from a few samples of output and begin predicting future values forever. If you use the strong, cryptographically secure PRNG that your operating system provides (for most operating systems, this means /dev/urandom), you don't have to worry about this undermining the security of your application.

Generating Random Strings at a Glance

There are three steps involved in providing developers with a reliable string generation library, and each step actually involves producing a utility that is useful in other contexts.

  1. Tapping into a reliable random data source.
  2. Converting random bytes into random integers.
  3. Using random integers to build a random string out of a list of desired characters.

Tapping into a Reliable Random Data Source in PHP Programs

The PHP 7 solution is to use random_bytes(), and if you're using PHP 7 by the time you read this blog post, please do use that for random byte strings.

Unfortunately, PHP 5 failed to offer a cross-platform function for generating random bytes until 5.3.0 was released with openssl_random_pseudo_bytes(). This shouldn't be a problem in 2015, since all of the earlier versions of PHP are not supported any more and everyone should be running a recent version, but a lot of people are still running PHP 5.2 (a flavor of negligence which WordPress continues to enable). Support for legacy PHP versions makes the whole process a mess.


Important Security Advisory

Our team has published a library to polyfill PHP7's random_bytes() function into PHP 5 projects. It's free, permissively licensed (MIT), and available on Github. Please refer to our library instead of the examples on this page, and pay attention to the issue tracker in case a vulnerability is discovered.


Update: openssl_random_pseudo_bytes() might not be a great solution either!

As Mason Malone points out on this Github issue for random_compat:

The openssl_random_pseudo_bytes() PHP function calls the RAND_psuedo_bytes() OpenSSL function, which the OpenSSL docs say should only be used for non-cryptographic purposes:

RAND_pseudo_bytes() has been deprecated. Users should use RAND_bytes() instead. RAND_pseudo_bytes() puts num pseudo-random bytes into buf. Pseudo-random byte sequences generated by RAND_pseudo_bytes() will be unique if they are of sufficient length, but are not necessarily unpredictable. They can be used for non-cryptographic purposes and for certain purposes in cryptographic protocols, but usually not for key generation etc.

Leigh dug through the ext/openssl source code and added some more insight:

  1. RAND_pseudo_bytes gets the default rand method which is going to be RAND_SSLeay unless you have an exotic setup

  2. Pseudo or not the same function is called with a final parameter to indicate "pseudo"

  3. pseudo is only referenced once within this function, to suppress an error message. The function returns 1 or 0.

  4. PHP only checks for a negative return value. This can only happen if there is no pseudo-random implementation, or the pseudo-random method is exotic

  5. Other bundled engines also behave this way - Basically, the only way to get a negative return and for PHP to cry about weak crypto is if the pseudorandom function isn't implemented, and you get NO bytes at all from OpenSSL.

This was fixed in August 2015, however:

Given the above, we cannot in good conscience recommend openssl_random_pseudo_bytes() as a CSPRNG. If you can use an alternative to openssl_random_pseudo_bytes(), please do so.


An implementation of PHP's random_bytes() function that works even in PHP 5.2 environments looks like this:

if (!function_exists('random_bytes')) {
    /**
     * EXAMPLE CODE -- DO NOT USE THIS IN YOUR PROJECTS! 
     * You want this instead:
     * https://github.com/paragonie/random_compat
     * 
     * PHP 5.2.0 - 5.6.x way to implement random_bytes()
     * 
     * @param int $bytes
     * @return string
     */
    function random_bytes($bytes)
    {
        if (!is_int($bytes) || $bytes < 1) {
             throw new InvalidArgumentException('random_bytes() expects a positive integer');
        }
        $buf = '';
        // See PHP bug #55169 for why 5.3.7 is required
        if (
            function_exists('mcrypt_create_iv') &&
            version_compare(PHP_VERSION, '5.3.7') >= 0
        ) {
            $buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
            if ($buf !== false) {
                return $buf;
            }
        }
        
        /**
         * Use /dev/urandom for random numbers
         * 
         * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
         */
        if (@is_readable('/dev/urandom')) {
            $fp = fopen('/dev/urandom', 'rb');
            if ($fp !== false) {
                $streamset = stream_set_read_buffer($fp, 0);
                if ($streamset === 0) {
                    $remaining = $bytes;
                    do {
                        $read = fread($fp, $remaining); 
                        if ($read === false) {
                            // We cannot safely read from urandom.
                            $buf = false;
                            break;
                        }
                        // Decrease the number of bytes returned from remaining
                        $remaining -= strlen($read);
                        $buf .= $read;
                    } while ($remaining > 0);
                    if ($buf !== false) {
                        return $buf;
                    }
                }
            }
        }
        /**
         * Windows with PHP < 5.3.0 will not have the function
         * openssl_random_pseudo_bytes() available, so let's use
         * CAPICOM to work around this deficiency.
         */
         if (class_exists('COM', false)) {
             try {
                 if ($buf === false) {
                     $buf = ''; // Make it a string, not false
                 }
                $util = new COM('CAPICOM.Utilities.1');
                $execs = 0;
                /**
                 * Let's not let it loop forever. If we run N times and fail to
                 * get N bytes of random data, then CAPICOM has failed us.
                 */
                do {
                    $buf .= base64_decode($util->GetRandom($bytes, 0));
                    if (strlen($buf) >= $bytes) { 
                        return substr($buf, 0, $bytes);
                    }
                    ++$execs;
                } while ($execs < $bytes);
            } catch (Exception $e) {
                unset($e); // Let's not let CAPICOM errors kill our app
            }
        }
        if (function_exists('openssl_random_pseudo_bytes')) {
            $secure = true;
            $buf = openssl_random_pseudo_bytes($bytes, $secure);
            if ($buf !== false && $secure) {
                return $buf;
            }
        }
        /**
         * We have reached the point of no return. Throw an exception.
         */
        throw new Exception('Better for PRNG failures to terminate than propagate');
    }
}

Now that we have a simple interface for grabbing an arbitrary number of random bytes from a strong random number generator, we can move on to step two.

Converting Random Bytes into Random Integers

There are many strategies online for generating random integers from raw binary data, but the only way to generate an uniformly distributed random integer in an arbitrary range from a raw binary string is to repeatedly generate and discard random values until one falls within the given range.


Important Security Advisory

If you are looking for a sane implementation, please use our polyfill library for random_int() instead of the example below. Our library is more likely to be up-to-date than our blog post.


if (!function_exists('random_int')) {    
    /**
     * Fetch a random integer between $min and $max inclusive
     * 
     * @param int $min
     * @param int $max
     * 
     * @throws Exception
     * 
     * @return int
     */
    function random_int($min, $max)
    {
        /**
         * Cast to an integer if we can, safely.
         */
        if (is_numeric($max)) {
            $max += 0;
        }
        if (
            is_float($max) &&
            $max > ~PHP_INT_MAX &&
            $max < PHP_INT_MAX
        ) {
            $max = (int) $max;
        }
        if (is_numeric($min)) {
            $min += 0;
        }
        if (
            is_float($min) &&
            $min > ~PHP_INT_MAX &&
            $min < PHP_INT_MAX
        ) {
            $min = (int) $min;
        }
        if (!is_int($max)) {
            throw new TypeError(
               'Maximum value must be an integer.'
            );
        }
        if (!is_int($min)) {
            throw new TypeError(
               'Minimum value must be an integer.'
            );
        }
        
        /**
         * Now that we've verified our weak typing system has given us an integer,
         * let's validate the logic then we can move forward with generating random
         * integers along a given range.
         */
        if ($min > $max) {
            throw new Error(
                'Minimum value must be less than or equal to the maximum value'
            );
        }
        if ($max === $min) {
            return $min;
        }
    
        /**
         * Initialize variables to 0
         * 
         * We want to store:
         * $bytes => the number of random bytes we need
         * $mask => an integer bitmask (for use with the &) operator
         *          so we can minimize the number of discards
         */
        $attempts = $bits = $bytes = $mask = $valueShift = 0;
    
        /**
         * At this point, $range is a positive number greater than 0. It might
         * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
         * a float and we will lose some precision.
         */
        $range = $max - $min;
    
        /**
         * Test for integer overflow:
         */
        if (!is_int($range)) {
            /**
             * Still safely calculate wider ranges.
             * Provided by @CodesInChaos, @oittaa
             * 
             * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
             * 
             * We use ~0 as a mask in this case because it generates all 1s
             * 
             * @ref https://eval.in/400356 (32-bit)
             * @ref http://3v4l.org/XX9r5  (64-bit)
             */
            $bytes = PHP_INT_SIZE;
            $mask = ~0;
        } else {
            /**
             * $bits is effectively ceil(log($range, 2)) without dealing with 
             * type juggling
             */
            while ($range > 0) {
                if ($bits % 8 === 0) {
                   ++$bytes;
                }
                ++$bits;
                $range >>= 1;
                $mask = $mask << 1 | 1;
            }
            $valueShift = $min;
        }
    
        /**
         * Now that we have our parameters set up, let's begin generating
         * random integers until one falls between $min and $max
         */
        do {
            /**
             * The rejection probability is at most 0.5, so this corresponds
             * to a failure probability of 2^-128 for a working RNG
             */
            if ($attempts > 128) {
                throw new Exception(
                    'random_int: RNG is broken - too many rejections'
                );
            }
    
            /**
             * Let's grab the necessary number of random bytes
             */
            $randomByteString = random_bytes($bytes);
            if ($randomByteString === false) {
                throw new Exception(
                    'Random number generator failure'
                );
            }
    
            /**
             * Let's turn $randomByteString into an integer
             * 
             * This uses bitwise operators (<< and |) to build an integer
             * out of the values extracted from ord()
             * 
             * Example: [9F] | [6D] | [32] | [0C] =>
             *   159 + 27904 + 3276800 + 201326592 =>
             *   204631455
             */
            $val = 0;
            for ($i = 0; $i < $bytes; ++$i) {
                $val |= ord($randomByteString[$i]) << ($i * 8);
            }
    
            /**
             * Apply mask
             */
            $val &= $mask;
            $val += $valueShift;
    
            ++$attempts;
            /**
             * If $val overflows to a floating point number,
             * ... or is larger than $max,
             * ... or smaller than $min,
             * then try again.
             */
        } while (!is_int($val) || $val > $max || $val < $min);
        return (int) $val;
    }
}

We frequently identify home-grown cryptography libraries that use the modulo operator to force the integers to fall within a given range. Unfortunately, this does not result in a uniform probability distribution.

Consider the following biased algorithm:

/**
 * DO NOT USE THIS FUNCTION!
 * It's presented here as a bad example, it is NOT production-ready!
 */
function biased_csprng($min, $max)
{
    $range = (int) ($max - $min);
    if ($range < 2) {
        return $min;
    }
    $bits = $bytes = $mask = 0;
    $tmp = $range;
    while ($tmp > 0) {
        if ($bits % 8 === 0) {
            ++$bytes;
        }
        ++$bits;
        $tmp >>= 1;
        $mask = $mask << 1 | 1;
    }
    $rval = random_bytes($bytes);
    if ($rval === false) {
        throw new Exception('Random number generator failure');
    }
    $val = 0;
    for ($i = 0; $i < $bytes; ++$i) {
        $val |= (ord($rval[$i]) << ($i * 8));
    }
    $val = ($val & $mask) % ($range + 1);
    // YOU SHOULD NOT BE USING THIS FUNCTION -- 
    // See https://paragonie.com/blog/2015/07/how-safely-generate-random-strings-and-integers-in-php
    // for disclaimers
    return (int) ($min + $val);
}

However, if $range is not an even power of 2, this will result in some values (closer to 0) appearing more often than others. Compare base-62 with base-64.

Using Random Numbers to Generate Random Strings

If you have a reliable source of unpredictable integers, generating a random string is completely straightforward.

/**
 * Note: See https://paragonie.com/b/JvICXzh_jhLyt4y3 for an alternative implementation
 */
function random_string($length = 26, $alphabet = 'abcdefghijklmnopqrstuvwxyz234567')
{
    if ($length < 1) {
        throw new InvalidArgumentException('Length must be a positive integer');
    }
    $str = '';
    $alphamax = strlen($alphabet) - 1;
    if ($alphamax < 1) {
        throw new InvalidArgumentException('Invalid alphabet');
    }
    for ($i = 0; $i < $length; ++$i) {
        $str .= $alphabet[random_int(0, $alphamax)];
    }
    return $str;
}

If you need a random alphanumeric string, you simply need to tell random_str() how many characters you need, and pass a string with all the possible values you're interested in.

random_str(20, 'abcdefghijklmnopqrstuvwxyz');
# "bvanjmiizleaueurmfkk"

random_str(20, '0123456789ABCDEFGHIJKLMNOPQRSTUVWZYZabcdefghijklmnopqrstuvwxyz');
# "z8ShTZD3fWOCYKTmlsdw"

Since random_str() uses random_int(), which uses random_bytes(), which is in turn cryptographically secure, we can guarantee that the strings it produce have a uniform distribution of possible values which do not follow a predictable pattern. The entropy of the output depends entirely on the $length and $alphabet values passed to the function rather than an inescapable 32 bit upper limit.

Update (July 7, 2015)

At the request of Sammy Kaye Powers, one of the authors of the random_bytes() and random_int() functions in PHP 7, we have rolled the above polyfill functions into a MIT licensed library called random_compat. Just run php composer.phar require paragonie/random_compat and you can enjoy these simplified APIs in your project today.

Update (July 15, 2015)

In light of new information pertaining to how openssl_random_pseudo_bytes() works under the hood, we no longer recommend it. When it breaks down (e.g. RAND_pseudo_bytes() is invoked before it's seeded), it fails silently. This is yet another reminder that Userspace CSPRNGs should not be trusted, and that providing entropy for cryptography purposes is the operating system's job.

Further Reading

About the Author

P.I.E. Staff

Paragon Initiative Enterprises

Paragon Initiative Enterprises is a Florida-based company that provides software consulting, application development, code auditing, and security engineering services. We specialize in PHP Security and applied cryptography.


Need Technology Consultants?

Will tomorrow bring costly and embarrassing data breaches? Or will it bring growth, success, and peace of mind?

Our team of technology consultants have extensive knowledge and experience with application security and web/application development.

We specialize in cryptography and secure PHP development.

Let's Work Together Towards Success

Our Security Newsletters

Want the latest from Paragon Initiative Enterprises delivered straight to your inbox? We have two newsletters to choose from.

The first mails quarterly and often showcases our behind-the-scenes projects.

The other is unscheduled and gives you a direct feed into the findings of our open source security research initiatives.

Quarterly Newsletter   Security Announcements