wp/wp-includes/random_compat/random_bytes_dev_urandom.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 19 3d72ae0968f4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
     2 /**
     2 /**
     3  * Random_* Compatibility Library 
     3  * Random_* Compatibility Library 
     4  * for using the new PHP 7 random_* API in PHP 5 projects
     4  * for using the new PHP 7 random_* API in PHP 5 projects
     5  * 
     5  * 
     6  * The MIT License (MIT)
     6  * The MIT License (MIT)
     7  * 
     7  *
     8  * Copyright (c) 2015 Paragon Initiative Enterprises
     8  * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
     9  * 
     9  * 
    10  * Permission is hereby granted, free of charge, to any person obtaining a copy
    10  * Permission is hereby granted, free of charge, to any person obtaining a copy
    11  * of this software and associated documentation files (the "Software"), to deal
    11  * of this software and associated documentation files (the "Software"), to deal
    12  * in the Software without restriction, including without limitation the rights
    12  * in the Software without restriction, including without limitation the rights
    13  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    13  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    28 
    28 
    29 if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
    29 if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
    30     define('RANDOM_COMPAT_READ_BUFFER', 8);
    30     define('RANDOM_COMPAT_READ_BUFFER', 8);
    31 }
    31 }
    32 
    32 
    33 if ( ! is_callable( 'random_bytes' ) ):
    33 if (!is_callable('random_bytes')) {
    34 /**
       
    35  * Unless open_basedir is enabled, use /dev/urandom for
       
    36  * random numbers in accordance with best practices
       
    37  * 
       
    38  * Why we use /dev/urandom and not /dev/random
       
    39  * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
       
    40  * 
       
    41  * @param int $bytes
       
    42  * 
       
    43  * @throws Exception
       
    44  * 
       
    45  * @return string
       
    46  */
       
    47 function random_bytes($bytes)
       
    48 {
       
    49     static $fp = null;
       
    50     /**
    34     /**
    51      * This block should only be run once
    35      * Unless open_basedir is enabled, use /dev/urandom for
       
    36      * random numbers in accordance with best practices
       
    37      *
       
    38      * Why we use /dev/urandom and not /dev/random
       
    39      * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
       
    40      *
       
    41      * @param int $bytes
       
    42      *
       
    43      * @throws Exception
       
    44      *
       
    45      * @return string
    52      */
    46      */
    53     if (empty($fp)) {
    47     function random_bytes($bytes)
       
    48     {
       
    49         static $fp = null;
    54         /**
    50         /**
    55          * We use /dev/urandom if it is a char device.
    51          * This block should only be run once
    56          * We never fall back to /dev/random
       
    57          */
    52          */
    58         $fp = fopen('/dev/urandom', 'rb');
    53         if (empty($fp)) {
    59         if (!empty($fp)) {
    54             /**
    60             $st = fstat($fp);
    55              * We use /dev/urandom if it is a char device.
    61             if (($st['mode'] & 0170000) !== 020000) {
    56              * We never fall back to /dev/random
    62                 fclose($fp);
    57              */
    63                 $fp = false;
    58             $fp = fopen('/dev/urandom', 'rb');
       
    59             if (!empty($fp)) {
       
    60                 $st = fstat($fp);
       
    61                 if (($st['mode'] & 0170000) !== 020000) {
       
    62                     fclose($fp);
       
    63                     $fp = false;
       
    64                 }
       
    65             }
       
    66 
       
    67             if (!empty($fp)) {
       
    68                 /**
       
    69                  * stream_set_read_buffer() does not exist in HHVM
       
    70                  *
       
    71                  * If we don't set the stream's read buffer to 0, PHP will
       
    72                  * internally buffer 8192 bytes, which can waste entropy
       
    73                  *
       
    74                  * stream_set_read_buffer returns 0 on success
       
    75                  */
       
    76                 if (is_callable('stream_set_read_buffer')) {
       
    77                     stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
       
    78                 }
       
    79                 if (is_callable('stream_set_chunk_size')) {
       
    80                     stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
       
    81                 }
    64             }
    82             }
    65         }
    83         }
    66 
    84 
       
    85         try {
       
    86             $bytes = RandomCompat_intval($bytes);
       
    87         } catch (TypeError $ex) {
       
    88             throw new TypeError(
       
    89                 'random_bytes(): $bytes must be an integer'
       
    90             );
       
    91         }
       
    92 
       
    93         if ($bytes < 1) {
       
    94             throw new Error(
       
    95                 'Length must be greater than 0'
       
    96             );
       
    97         }
       
    98 
       
    99         /**
       
   100          * This if() block only runs if we managed to open a file handle
       
   101          *
       
   102          * It does not belong in an else {} block, because the above
       
   103          * if (empty($fp)) line is logic that should only be run once per
       
   104          * page load.
       
   105          */
    67         if (!empty($fp)) {
   106         if (!empty($fp)) {
    68             /**
   107             /**
    69              * stream_set_read_buffer() does not exist in HHVM
   108              * @var int
    70              * 
       
    71              * If we don't set the stream's read buffer to 0, PHP will
       
    72              * internally buffer 8192 bytes, which can waste entropy
       
    73              * 
       
    74              * stream_set_read_buffer returns 0 on success
       
    75              */
   109              */
    76             if (function_exists('stream_set_read_buffer')) {
   110             $remaining = $bytes;
    77                 stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
   111 
    78             }
   112             /**
    79             if (function_exists('stream_set_chunk_size')) {
   113              * @var string|bool
    80                 stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
   114              */
       
   115             $buf = '';
       
   116 
       
   117             /**
       
   118              * We use fread() in a loop to protect against partial reads
       
   119              */
       
   120             do {
       
   121                 /**
       
   122                  * @var string|bool
       
   123                  */
       
   124                 $read = fread($fp, $remaining);
       
   125                 if (!is_string($read)) {
       
   126                     if ($read === false) {
       
   127                         /**
       
   128                          * We cannot safely read from the file. Exit the
       
   129                          * do-while loop and trigger the exception condition
       
   130                          *
       
   131                          * @var string|bool
       
   132                          */
       
   133                         $buf = false;
       
   134                         break;
       
   135                     }
       
   136                 }
       
   137                 /**
       
   138                  * Decrease the number of bytes returned from remaining
       
   139                  */
       
   140                 $remaining -= RandomCompat_strlen($read);
       
   141                 /**
       
   142                  * @var string|bool
       
   143                  */
       
   144                 $buf = $buf . $read;
       
   145             } while ($remaining > 0);
       
   146 
       
   147             /**
       
   148              * Is our result valid?
       
   149              */
       
   150             if (is_string($buf)) {
       
   151                 if (RandomCompat_strlen($buf) === $bytes) {
       
   152                     /**
       
   153                      * Return our random entropy buffer here:
       
   154                      */
       
   155                     return $buf;
       
   156                 }
    81             }
   157             }
    82         }
   158         }
    83     }
       
    84 
   159 
    85     try {
   160         /**
    86         $bytes = RandomCompat_intval($bytes);
   161          * If we reach here, PHP has failed us.
    87     } catch (TypeError $ex) {
   162          */
    88         throw new TypeError(
   163         throw new Exception(
    89             'random_bytes(): $bytes must be an integer'
   164             'Error reading from source device'
    90         );
   165         );
    91     }
   166     }
    92 
       
    93     if ($bytes < 1) {
       
    94         throw new Error(
       
    95             'Length must be greater than 0'
       
    96         );
       
    97     }
       
    98 
       
    99     /**
       
   100      * This if() block only runs if we managed to open a file handle
       
   101      * 
       
   102      * It does not belong in an else {} block, because the above 
       
   103      * if (empty($fp)) line is logic that should only be run once per
       
   104      * page load.
       
   105      */
       
   106     if (!empty($fp)) {
       
   107         $remaining = $bytes;
       
   108         $buf = '';
       
   109 
       
   110         /**
       
   111          * We use fread() in a loop to protect against partial reads
       
   112          */
       
   113         do {
       
   114             $read = fread($fp, $remaining); 
       
   115             if ($read === false) {
       
   116                 /**
       
   117                  * We cannot safely read from the file. Exit the
       
   118                  * do-while loop and trigger the exception condition
       
   119                  */
       
   120                 $buf = false;
       
   121                 break;
       
   122             }
       
   123             /**
       
   124              * Decrease the number of bytes returned from remaining
       
   125              */
       
   126             $remaining -= RandomCompat_strlen($read);
       
   127             $buf .= $read;
       
   128         } while ($remaining > 0);
       
   129         
       
   130         /**
       
   131          * Is our result valid?
       
   132          */
       
   133         if ($buf !== false) {
       
   134             if (RandomCompat_strlen($buf) === $bytes) {
       
   135                 /**
       
   136                  * Return our random entropy buffer here:
       
   137                  */
       
   138                 return $buf;
       
   139             }
       
   140         }
       
   141     }
       
   142 
       
   143     /**
       
   144      * If we reach here, PHP has failed us.
       
   145      */
       
   146     throw new Exception(
       
   147         'Error reading from source device'
       
   148     );
       
   149 }
   167 }
   150 endif;