user_password() uses the following code.

  // Loop the number of times specified by $length.
  for ($i = 0; $i < $length; $i++) {
    do {

      // Find a secure random number within the range needed.
      $index = ord(random_bytes(1));
    } while ($index > $len);

    // Each iteration, pick a random character from the
    // allowable string and append it to the password:
    $pass .= $allowable_characters[$index];

It's quite an inefficient way to get 10 numbers between 0 and 56. In a simple test I did, I had to call ord(random_bytes(1)) 51 times to get 10 numbers between 0 and 56. (The result indeed changes each time the test is done.)

105 216 236 199 33 78 25 98 23 46 122 177 131 80 252 22 202 16 157 201 109 161 188 195 237 174 81 133 59 62 140 65 125 11 47 147 188 66 131 228 134 157 186 91 56 239 194 168 186 183 51 194 202 77 32 161 31 93 87 52 48 121 164 168 206 129 229 215 221 252 84 111 49 165 28 36 64 81 53 220 208 182 182 92 126 91 68 2 114 242 46 84 234 91 174 45 126 145 239 208 151 234 104 29 233 27 183 27 236 122 91 34 229 175 217 57 226 166 130 25 150 229 90 116 237 116 175 186 178 221 214 132 198 28 213 209 132 162 232 194 139 115 170 0 133 193 157 220 23 174 60 61 114 195 143 6 97 113 26 105 163 26 80 29 146 76 201 98 189 205 201 79 172 150 187 255 163 111 204 52 176 212 169 23 33 176 231 19 244 195 230 208 232 218 208 142 175 32 202 54 215 173 138 247 62 189 34 185 113 85 84 16 155 115 205 240 10 154 240 4 129 98 182 35 203 77 220 153 156 166 83 168 54 71 100 17 12 43 221 225 53 25 210 13 117 21 47 55 121 223 168 70 5 251 232 138 35 88 243 250 51 151 50 30 219 129 178 255 215 26 213 86 145 255 142 87 214 17 46 219 87 14 40 100 31 31 212 112 181 13 214 176 237 131 101 229 229 241 109 122

This is expected: random_bytes() generates a string of cryptographic random bytes and the probability to get a byte in a range should not be higher than the probability to get a byte in a different range.

PHP 7 offers random_int() which generates cryptographic random integers.

Calling random_int() 300 times (as in the previous test), I get the following numbers.

20 24 3 48 45 52 30 3 19 2 24 18 17 43 21 31 2 3 40 0 13 29 15 4 25 43 19 8 4 40 51 20 25 52 20 33 15 21 23 27 33 4 51 44 30 24 37 9 41 39 41 39 47 54 17 53 14 53 24 46 30 14 30 1 33 8 23 16 1 28 45 26 10 25 15 19 29 52 21 21 6 27 56 51 21 5 6 16 55 18 51 40 25 5 9 56 31 40 6 22 23 7 36 46 17 23 12 51 50 45 33 51 9 13 6 43 0 41 11 3 5 30 42 19 47 10 34 1 22 12 32 8 5 24 17 41 30 21 47 55 11 49 56 50 11 38 23 26 50 29 32 2 39 27 24 55 49 15 26 52 12 55 38 11 32 9 43 32 16 47 27 17 8 53 29 2 15 27 3 54 49 38 28 53 15 37 53 20 0 11 3 14 29 49 4 44 0 9 33 18 9 47 33 43 37 41 21 52 8 41 34 26 40 32 53 14 1 26 36 54 54 34 14 23 15 25 8 11 52 21 2 20 45 56 22 10 25 30 35 4 18 42 46 13 14 55 36 42 19 32 8 32 34 21 33 55 43 14 50 12 50 33 38 36 34 47 6 10 5 1 44 2 16 33 40 56 50 30 7 26 55 31 53 56 35 17 10 2 10 6 12 32 47 21 22 5 29 9 44 51

user_password() should be updated to use random_int() instead of random_bytes().

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

kiamlaluno created an issue. See original summary.

apaderno’s picture

Issue summary: View changes
apaderno’s picture

Status: Active » Needs review
FileSize
719 bytes
pandaski’s picture

+++ b/core/modules/user/user.module
@@ -312,14 +312,9 @@ function user_password($length = 10) {
-      $index = ord(random_bytes(1));
...
     // Each iteration, pick a random character from the

The change looks fine.

A quick question to the random_int parameters - min and max

They are per-defined constants in PHP. Will this lead to some issues in custom settings?

min

The lowest value to be returned, which must be PHP_INT_MIN or higher.

max

The highest value to be returned, which must be less than or equal to PHP_INT_MAX.

apaderno’s picture

The parameters passed to random_int() don't depend from custom settings, but are 0, and the length of the string contained in $allowable_characters minus 1. They surely respect the constraints reported in the documentation for random_int(), even on 32-bit PHP.

longwave’s picture

+++ b/core/modules/user/user.module
@@ -312,14 +312,9 @@ function user_password($length = 10) {
   // Loop the number of times specified by $length.

We have more comments than code now. Do we really need a comment that describes what a simple "for" loop does?

apaderno’s picture

@longwave I agree there are comments that describe something that is explicit from the code, for example the Declare the password as a blank string. one. (I would argue the variable is not being declared, but initialized.) The only useful comment is the one explaining why some alphanumeric characters have been removed from the allowed characters.

I removed some comments and added the documentation comments for parameters and returned value.

apaderno’s picture

I would also rename the $allowable_characters variable $allowed_characters.

longwave’s picture

Great improvements! We have $len and $length, I wonder if renaming $len to $max (as in, the maximum random number) makes this slightly more readable?

apaderno’s picture

longwave’s picture

Status: Needs review » Reviewed & tested by the community

A nice modernisation, and documentation improvements on top.

apaderno’s picture

I changed the comment I added for $max from The maximum value for the random integer we get from random_int(). to The maximum integer we want from random_int(). (This is my last change, if nobody points out other changes that should be done.)

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 06f5e2503d to 9.0.x and d0c7aa1beb to 8.9.x. Thanks!

----------------------------------------------------------------------
FOUND 3 ERRORS AFFECTING 3 LINES
----------------------------------------------------------------------
 202 | ERROR | [x] Parameter comment indentation must be 3 spaces,
     |       |     found 2 spaces
 205 | ERROR | [x] Return comment indentation must be 3 spaces, found
     |       |     2 spaces
 215 | ERROR | [x] Whitespace found at end of line
----------------------------------------------------------------------
PHPCBF CAN FIX THE 3 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Fixing the above code style errors on commit.

diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 3291f7878b..fec5451b8d 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -199,10 +199,10 @@ function user_validate_name($name) {
  * Generate a random alphanumeric password.
  *
  * @param int $length
- *  The desired password length, in characters.
+ *   The desired password length, in characters.
  *
  * @return string
- *  The generated random password.
+ *   The generated random password.
  */
 function user_password($length = 10) {
   // This variable contains the list of allowed characters for the password.
@@ -212,7 +212,7 @@ function user_password($length = 10) {
 
   // The maximum integer we want from random_int().
   $max = strlen($allowed_characters) - 1;
-  
+
   $pass = '';
 
   for ($i = 0; $i < $length; $i++) {

  • alexpott committed 06f5e25 on 9.0.x
    Issue #3107371 by kiamlaluno, longwave: Update user_password() for PHP 7
    

  • alexpott committed d0c7aa1 on 8.9.x
    Issue #3107371 by kiamlaluno, longwave: Update user_password() for PHP 7...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.