4
\$\begingroup\$

After reading an article on Skull Security mentioning the potential weakness of php's mt_rand function because of weak auto-seeding (http://ow.ly/4nrne), I decided to see what -- if any -- entropy I could find available from within php. The idea is to have enough (weak) sources that even if one or two are manipulated, lost or recovered, there's enough left to thwart brute force against the resulting passwords later.

Hopefully the result is both readable and usable, although I don't expect it to be production-quality.

<?php
  /**
   * Return a random password.
   *
       * v1.01
   * Jumps through many hoops to attempt to overcome autoseed weakness of php's mt_rand function
   *
   */
  function myRandomPassword() {
        // Change this for each installation
        $localsecret = 'qTgppE9T2c';
    // Determine length of generated password
    $pwlength = 10;
    // Character set for password
    $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz0123456789';
    $l = strlen( $pwchars ) - 1;
    // Get a little bit of entropy from sources that should be inaccessible to outsiders and non-static
    $dat = getrusage(); // gather some information from the running system
    $datline = md5(implode($dat)); // wash using md5 -- it's fast and there's not enough entropy to warrant longer hash
    $hardToGuess = $datline;
    $self = __FILE__; // a file the script should have read access to (itself)
    $stat = stat($self); // information about file such as inode, accessed time, uid, guid
    $statline = md5(implode($stat)); // wash
    $hardToGuess .= $statline;
    $preseed = md5(microtime()) . getmypid() . $hardToGuess . memory_get_usage() . disk_free_space('.') . $localsecret;
    $seed = sha1( $preseed ); // final wash, longer hash
    // Seed the mt_rand() function with a better seed than the standard one
    mt_srand ($seed);
    // Pick characters from the lineup, using the seeded mt_rand function
    $pw = '';
    for ( $i = 0; $i < $pwlength; $i++ ) {
      $pw .= $pwchars{ mt_rand( 0, $l ) };
    }
    // Return the result
    return $pw;
  }

echo myRandomPassword();
?>

Revision 1.01 adds a local secret.

\$\endgroup\$
5
  • \$\begingroup\$ Just realized, while reviewing my own code the day after, that the whole thing is currently useless because the mt_srand function expects an integer as seed. \$\endgroup\$
    – Niels2000
    Mar 28, 2011 at 10:34
  • \$\begingroup\$ It's easy to cast the seed as a number (using hexdec, for example) but the problem is that the number will be too large to fit as an integer and become a float. \$\endgroup\$
    – Niels2000
    Mar 28, 2011 at 11:04
  • \$\begingroup\$ So, basically, I could use a suggestion on how to reduce the float value to an integer that fits into the current system's integer size, but does not lose too much of the gathered entropy in the process. \$\endgroup\$
    – Niels2000
    Mar 28, 2011 at 11:12
  • \$\begingroup\$ Having done some searching, I believe the best implementation of a crc64-algorithm is currently this one, written in C: bioinfadmin.cs.ucl.ac.uk/downloads/crc64 All I have to do now is convert it to php. Too bad I don't know C, this is going to be a challenge. \$\endgroup\$
    – Niels2000
    Apr 2, 2011 at 12:14
  • \$\begingroup\$ OK, so I learned elsewhere that the rand() and mt_rand() functions are actually separately seeded. This should mean that using both means you have to attack both, at the same time, to recover the result (unless attacking the 32-bit-problem). Essentially, we can do no worse by adding rand() to the mix, and probably make attacks quite a bit harder. I've implemented this by using str_shuffle on the character set, because that invokes the rand() function, not mt_rand(), according to the documentation. As a side effect, this code now "works" on 64-bit systems, but very weakly. \$\endgroup\$
    – Niels2000
    Apr 3, 2011 at 10:34

1 Answer 1

2
\$\begingroup\$

I actually don't get the point of injecting so much system information into the mt_srand function. Looks like a total (and maybe even pointless) paranoia :)

But here you go with a cleaner code:

<?php
/**
* Random password generator
* v2.0
*/

define('APP_SECRET_KEY', 'qTgppE9T2c');

function randomPassword($length=10) {
  $charset = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz0123456789';
  $charsetSize = strlen($charset) - 1;

  // Seeding the generator with a bunch of different system data and the secret key
  mt_srand(crc32(md5(microtime())
    . getmypid()
    . md5(implode(getrusage()))
    . md5(implode(stat(__FILE__)))
    . memory_get_usage()
    . disk_free_space('.')
    . APP_SECRET_KEY)
  );

  $password = '';
  foreach (range(1, $length) as $_)
    $password .= $charset{mt_rand(0, $charsetSize)};

  return $password;
}

echo randomPassword(), "\n";

Maybe you'll like the more perverted superslow version which returns CRC32 of randomly ordered entropy each time you generate a new symbol.

<?php
/**
* Random password generator
* v2.1
*/

function randomPassword($length=10) {
  $charset = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz0123456789';
  $charsetSize = strlen($charset) - 1;

  $seeders = array(
    function () { return md5(microtime()); },
    function () { return md5(getmypid()); },
    function () { return md5(implode(getrusage())); },
    function () { return memory_get_usage(); },
    function () { return disk_free_space('.'); }
  );

  $randomSeed = function () use ($seeders) {
    shuffle($seeders);

    $entropy = '';
    foreach ($seeders as $seeder)
      $entropy .= $seeder();

    return crc32($entropy);
  };

  $password = '';
  foreach (range(1, $length) as $_) {
    mt_srand($randomSeed());
    $password .= $charset{mt_rand(0, $charsetSize)};
  }

  return $password;
}

echo randomPassword(), "\n";
\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the input! Your code is certainly much nicer than mine, in several ways. And the idea of using crc32 to cast the entropy into an integer is a good one -- except for one small thing. The primary point of the excersize is to increase both the entropy and the pool of intitial seed states to more than 32 bit. \$\endgroup\$
    – Niels2000
    Mar 31, 2011 at 23:29
  • \$\begingroup\$ I understand why it may seem pointless to seed the random number generator in a stronger fashion than what PHP already does. But the problem is that because the random generator is actually only pseudo-random, based on the seed, it will return the exact same series of values every time if called with the same seed.This means that if you know the function, and you know that the seed it limited to only 32 bits, this means that there's only (2^32=) 4.294.967.296 potential passwords, which is a lot less than you can check in a reasonable time frame. \$\endgroup\$
    – Niels2000
    Mar 31, 2011 at 23:37
  • \$\begingroup\$ More information on the problems of weak seeds at suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers and the blog I linked in the original post. \$\endgroup\$
    – Niels2000
    Mar 31, 2011 at 23:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.