/**
 * If the libsodium PHP extension is loaded, we'll use it above any other
 * solution.
 *
 * libsodium-php project:
 * @ref https://github.com/jedisct1/libsodium-php
 *
 * @param int $bytes
 *
 * @throws Exception
 *
 * @return string
 */
function random_bytes($bytes)
{
    try {
        $bytes = RandomCompat_intval($bytes);
    } catch (TypeError $ex) {
        throw new TypeError('random_bytes(): $bytes must be an integer');
    }
    if ($bytes < 1) {
        throw new Error('Length must be greater than 0');
    }
    /**
     * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
     * generated in one invocation.
     */
    if ($bytes > 2147483647) {
        $buf = '';
        for ($i = 0; $i < $bytes; $i += 1073741824) {
            $n = $bytes - $i > 1073741824 ? 1073741824 : $bytes - $i;
            $buf .= \Sodium\randombytes_buf($n);
        }
    } else {
        $buf = \Sodium\randombytes_buf($bytes);
    }
    if ($buf !== false) {
        if (RandomCompat_strlen($buf) === $bytes) {
            return $buf;
        }
    }
    /**
     * If we reach here, PHP has failed us.
     */
    throw new Exception('Could not gather sufficient random data');
}
 public function testIntval()
 {
     if (!function_exists('RandomCompat_intval')) {
         return $this->markTestSkipped('We don\' need to test this in PHP 7.');
     }
     // Equals
     $this->assertEquals(abs(RandomCompat_intval(-4.5)), abs(RandomCompat_intval(4.5)));
     // True
     $this->assertTrue(is_int(RandomCompat_intval(PHP_INT_MAX, true)));
     $this->assertTrue(is_int(RandomCompat_intval(~PHP_INT_MAX, true)));
     $this->assertTrue(is_int(RandomCompat_intval(~PHP_INT_MAX + 1, true)));
     $this->assertTrue(is_int(RandomCompat_intval("1337e3", true)));
     $this->assertTrue(is_int(RandomCompat_intval("1.", true)));
     // False
     $this->assertFalse(is_int(RandomCompat_intval((double) PHP_INT_MAX, true)));
     $this->assertFalse(is_int(RandomCompat_intval((double) ~PHP_INT_MAX, true)));
     $this->assertFalse(is_int(RandomCompat_intval(PHP_INT_MAX + 1, true)));
     $this->assertFalse(is_int(RandomCompat_intval(~PHP_INT_MAX - 1, true)));
     $this->assertFalse(is_int(RandomCompat_intval(~PHP_INT_MAX - 0.1, true)));
     $this->assertFalse(is_int(RandomCompat_intval(PHP_INT_MAX + 0.1, true)));
     $this->assertFalse(is_int(RandomCompat_intval("hello", true)));
     if (PHP_INT_SIZE === 8) {
         $this->assertFalse(is_int(RandomCompat_intval("-9223372036854775809", true)));
         $this->assertTrue(is_int(RandomCompat_intval("-9223372036854775808", true)));
         $this->assertFalse(is_int(RandomCompat_intval("9223372036854775808", true)));
         $this->assertTrue(is_int(RandomCompat_intval("9223372036854775807", true)));
     } else {
         $this->assertFalse(is_int(RandomCompat_intval("2147483648", true)));
         $this->assertTrue(is_int(RandomCompat_intval("2147483647", true)));
         $this->assertFalse(is_int(RandomCompat_intval("-2147483649", true)));
         $this->assertTrue(is_int(RandomCompat_intval("-2147483648", true)));
     }
 }
 /**
  * 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.
  *
  * @param int $bytes
  *
  * @throws Exception
  *
  * @return string
  */
 function random_bytes($bytes)
 {
     try {
         $bytes = RandomCompat_intval($bytes);
     } catch (TypeError $ex) {
         throw new TypeError('random_bytes(): $bytes must be an integer');
     }
     if ($bytes < 1) {
         throw new Error('Length must be greater than 0');
     }
     $buf = '';
     $util = new COM('CAPICOM.Utilities.1');
     $execCount = 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 (RandomCompat_strlen($buf) >= $bytes) {
             /**
              * Return our random entropy buffer here:
              */
             return RandomCompat_substr($buf, 0, $bytes);
         }
         ++$execCount;
     } while ($execCount < $bytes);
     /**
      * If we reach here, PHP has failed us.
      */
     throw new Exception('Could not gather sufficient random data');
 }
/**
 * Since openssl_random_pseudo_bytes() uses openssl's
 * RAND_pseudo_bytes() API, which has been marked as deprecated by the
 * OpenSSL team, this is our last resort before failure.
 *
 * @ref https://www.openssl.org/docs/crypto/RAND_bytes.html
 *
 * @param int $bytes
 *
 * @throws Exception
 *
 * @return string
 */
function random_bytes($bytes)
{
    try {
        $bytes = RandomCompat_intval($bytes);
    } catch (TypeError $ex) {
        throw new TypeError('random_bytes(): $bytes must be an integer');
    }
    if ($bytes < 1) {
        throw new Error('Length must be greater than 0');
    }
    $secure = true;
    /**
     * $secure is passed by reference. If it's set to false, fail. Note
     * that this will only return false if this function fails to return
     * any data.
     *
     * @ref https://github.com/paragonie/random_compat/issues/6#issuecomment-119564973
     */
    $buf = openssl_random_pseudo_bytes($bytes, $secure);
    if ($buf !== false && $secure) {
        if (RandomCompat_strlen($buf) === $bytes) {
            return $buf;
        }
    }
    /**
     * If we reach here, PHP has failed us.
     */
    throw new Exception('Could not gather sufficient random data');
}
Example #5
0
 public function testIntval()
 {
     if (!function_exists('RandomCompat_intval')) {
         return $this->markTestSkipped('We don\' need to test this in PHP 7.');
     }
     // Equals
     $this->assertEquals(abs(RandomCompat_intval(-4.5)), abs(RandomCompat_intval(4.5)));
     // True
     $this->assertTrue(is_int(RandomCompat_intval(PHP_INT_MAX, true)));
     $this->assertTrue(is_int(RandomCompat_intval(~PHP_INT_MAX, true)));
     $this->assertTrue(is_int(RandomCompat_intval(~PHP_INT_MAX + 1, true)));
     // False
     $this->assertFalse(is_int(RandomCompat_intval((double) PHP_INT_MAX, true)));
     $this->assertFalse(is_int(RandomCompat_intval((double) ~PHP_INT_MAX, true)));
     $this->assertFalse(is_int(RandomCompat_intval(PHP_INT_MAX + 1, true)));
     $this->assertFalse(is_int(RandomCompat_intval(~PHP_INT_MAX - 1, true)));
     $this->assertFalse(is_int(RandomCompat_intval(PHP_INT_MAX - 0.01, true)));
     $this->assertFalse(is_int(RandomCompat_intval(~PHP_INT_MAX + 0.01, true)));
 }
 /**
  * Powered by ext/mcrypt (and thankfully NOT libmcrypt)
  *
  * @ref https://bugs.php.net/bug.php?id=55169
  * @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386
  *
  * @param int $bytes
  *
  * @throws Exception
  *
  * @return string
  */
 function random_bytes($bytes)
 {
     try {
         $bytes = RandomCompat_intval($bytes);
     } catch (TypeError $ex) {
         throw new TypeError('random_bytes(): $bytes must be an integer');
     }
     if ($bytes < 1) {
         throw new Error('Length must be greater than 0');
     }
     $buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
     if ($buf !== false && RandomCompat_strlen($buf) === $bytes) {
         /**
          * Return our random entropy buffer here:
          */
         return $buf;
     }
     /**
      * If we reach here, PHP has failed us.
      */
     throw new Exception('Could not gather sufficient random data');
 }
/**
 * Unless open_basedir is enabled, use /dev/urandom for
 * random numbers in accordance with best practices
 * 
 * Why we use /dev/urandom and not /dev/random
 * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
 * 
 * @param int $bytes
 * 
 * @throws Exception
 * 
 * @return string
 */
function random_bytes($bytes)
{
    static $fp = null;
    /**
     * This block should only be run once
     */
    if (empty($fp)) {
        /**
         * We use /dev/urandom if it is a char device.
         * We never fall back to /dev/random
         */
        $fp = fopen('/dev/urandom', 'rb');
        if (!empty($fp)) {
            $st = fstat($fp);
            if (($st['mode'] & 0170000) !== 020000) {
                fclose($fp);
                $fp = false;
            }
        }
        if (!empty($fp)) {
            /**
             * stream_set_read_buffer() does not exist in HHVM
             * 
             * If we don't set the stream's read buffer to 0, PHP will
             * internally buffer 8192 bytes, which can waste entropy
             * 
             * stream_set_read_buffer returns 0 on success
             */
            if (function_exists('stream_set_read_buffer')) {
                stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
            }
            if (function_exists('stream_set_chunk_size')) {
                stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
            }
        }
    }
    try {
        $bytes = RandomCompat_intval($bytes);
    } catch (TypeError $ex) {
        throw new TypeError('random_bytes(): $bytes must be an integer');
    }
    if ($bytes < 1) {
        throw new Error('Length must be greater than 0');
    }
    /**
     * This if() block only runs if we managed to open a file handle
     * 
     * It does not belong in an else {} block, because the above 
     * if (empty($fp)) line is logic that should only be run once per
     * page load.
     */
    if (!empty($fp)) {
        $remaining = $bytes;
        $buf = '';
        /**
         * We use fread() in a loop to protect against partial reads
         */
        do {
            $read = fread($fp, $remaining);
            if ($read === false) {
                /**
                 * We cannot safely read from the file. Exit the
                 * do-while loop and trigger the exception condition
                 */
                $buf = false;
                break;
            }
            /**
             * Decrease the number of bytes returned from remaining
             */
            $remaining -= RandomCompat_strlen($read);
            $buf .= $read;
        } while ($remaining > 0);
        /**
         * Is our result valid?
         */
        if ($buf !== false) {
            if (RandomCompat_strlen($buf) === $bytes) {
                /**
                 * Return our random entropy buffer here:
                 */
                return $buf;
            }
        }
    }
    /**
     * If we reach here, PHP has failed us.
     */
    throw new Exception('Error reading from source device');
}
Example #8
0
/**
 * Fetch a random integer between $min and $max inclusive
 *
 * @param int $min
 * @param int $max
 *
 * @throws Exception
 *
 * @return int
 */
function random_int($min, $max)
{
    /**
     * Type and input logic checks
     *
     * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
     * (non-inclusive), it will sanely cast it to an int. If you it's equal to
     * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
     * lose precision, so the <= and => operators might accidentally let a float
     * through.
     */
    try {
        $min = RandomCompat_intval($min);
    } catch (TypeError $ex) {
        throw new TypeError('random_int(): $min must be an integer');
    }
    try {
        $max = RandomCompat_intval($max);
    } catch (TypeError $ex) {
        throw new TypeError('random_int(): $max 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;
}