Making crypt_random more secure

Get help with using the PHP Secure Communications Library.

Moderator: Nuxius

Forum rules
The purpose of this forum is to provide support for phpseclib, a pure PHP SSH / SFTP / RSA library.

Posts by new users are held in a moderation queue and are not publicly visible until the post is approved.

Making crypt_random more secure

Postby wise.man » Fri Mar 16, 2012 7:57 pm

I think crypt_random will fallback to mt_rand on many servers, because I think openssl_random_pseudo_bytes is not available on many servers and, due to open_basedir, there is no access to /dev/urandom even on many linux servers too.

thus crypt_random security is comparable to mt_rand security in many cases.

u know mt_rand is really weak because it uses a 32 bit (on 32 bit machines) or 64 bit (on 64 bit machines) seed and thus has only 2^32 or 2^64 initial states (specially 2^32 is a disaster!).

i also noticed that it apparently uses getmypid and time functions for generating its seed, that I think can make it even more weak. for example a user knows the time of his own request and therefore can narrow down the time parameter search space very well.

i am eager to hear about any ideas and solutions.

also i have some ideas myself to make crypt_random somewhat stronger in such cases.

i see u have used this:
Code: Select all
extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF)))));
return abs($random) % ($max - $min) + $min;

i think we can easily add microtime to it to increase the time resolution and add some more randomness due to the noise in the execution times of different parts of the program code:
Code: Select all
extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF).microtime()))));
return abs($random) % ($max - $min) + $min;

i think we can even add other parameters like user IP and UserAgent to it. They can add some more randomness/security at least against guys other than the user himself.
also i thought about using a server secret random string to add to the parameters too. this is a string that the site owner sets by hand during web site/app setup.
we can also generate a random string at program setup automatically, store it on the server, and use it in addition to other parameters.

of course we can use other sources like database contents to collect more entropy, but I think for now and the simple solution we can and should add to the security of crypt_random somewhat.
I think things like microtime are worth adding.

I am writing a register and login system and really need a good random source for its security.
wise.man
Traveler
 
Posts: 14
Joined: Tue Nov 15, 2011 5:54 pm

Re: Making crypt_random more secure

Postby wise.man » Sat Mar 17, 2012 12:21 pm

on this page: http://stackoverflow.com/questions/5774 ... s-platform
a guy has said:
In PHP, you should be able to use mcrypt_create_iv() with MCRYPT_DEV_URANDOM, which uses /dev/urandom on Unix and (apparently) CryptGenRandom on Windows.

if mcrypt_create_iv is exempt from open_basedir restriction, probably it's a good idea to add trying it to you crypt_random.

i have another suggestion:
add a facility to the crypt_random function so that the user can easily tell what random source it has used.
this way we can easily determine the security of random output and decide upon it what to do.
maybe u can even add an extra parameter to the crypt_random to tell it to use only high security randomness sources or rise an error if no such sources are available.
wise.man
Traveler
 
Posts: 14
Joined: Tue Nov 15, 2011 5:54 pm

Re: Making crypt_random more secure

Postby TerraFrost » Sat Mar 17, 2012 9:26 pm

I need to redo crypt_random entirely. I don't like it at all lol. A better way to do it imho would be to make it so it gives you a string that's x bytes long. If you want to interpret that string as an integer you can do unpack('N', ...) yourself.

Thanks for the suggestions!
TerraFrost
Legendary Guard
 
Posts: 12357
Joined: Wed Dec 04, 2002 6:37 am

Re: Making crypt_random more secure

Postby wise.man » Mon Mar 19, 2012 10:40 am

i modified crypt_random for my own project:
Code: Select all
function crypt_random($min = 0, $max = 0x7FFFFFFF)
{
    if ($min == $max) {
        return $min;
    }

    if (function_exists('openssl_random_pseudo_bytes')) {
        // openssl_random_pseudo_bytes() is slow on windows per the following:
        // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
        if ((PHP_OS & "\xDF\xDF\xDF") !== 'WIN') { // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
            extract(unpack('Nrandom', openssl_random_pseudo_bytes(4)));
            return abs($random) % ($max - $min) + $min;
        }
    }

    // see http://en.wikipedia.org/wiki//dev/random
    static $urandom = true;
    if ($urandom === true) {
        // Warning's will be output unles the error suppression operator is used.  Errors such as
        // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
        $urandom = @fopen('/dev/urandom', 'rb');
    }
    if (!is_bool($urandom)) {
        extract(unpack('Nrandom', fread($urandom, 4)));

        // say $min = 0 and $max = 3.  if we didn't do abs() then we could have stuff like this:
        // -4 % 3 + 0 = -1, even though -1 < $min
        return abs($random) % ($max - $min) + $min;
    }
   
   
   if(function_exists('mcrypt_create_iv') and version_compare(PHP_VERSION, '5.3.0', '>=')) {
      @$tmp=mcrypt_create_iv(4, MCRYPT_DEV_URANDOM);
      if($tmp!==false) {
         extract(unpack('Nrandom', $tmp));
         return abs($random) % ($max - $min) + $min;
      }
   }
   

    /* Prior to PHP 4.2.0, mt_srand() had to be called before mt_rand() could be called.
       Prior to PHP 5.2.6, mt_rand()'s automatic seeding was subpar, as elaborated here:

       http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/

       The seeding routine is pretty much ripped from PHP's own internal GENERATE_SEED() macro:

       http://svn.php.net/viewvc/php/php-src/tags/php_5_3_2/ext/standard/php_rand.h?view=markup */
   static $seeded;
    if (!isset($seeded) and version_compare(PHP_VERSION, '5.2.5', '<=')) {
        $seeded = true;
        mt_srand(fmod(time() * getmypid(), 0x7FFFFFFF) ^ fmod(1000000 * lcg_value(), 0x7FFFFFFF));
    }

   $pee=microtime(); // pee: Possible Extra Entropy
   @$pee.=$_SERVER['REMOTE_ADDR'].$_SERVER['REMOTE_PORT'].$_SERVER['HTTP_USER_AGENT'];
   
    extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF).$pee))));
    return abs($random) % ($max - $min) + $min;

}

i added:
- mcrypt_create_iv method
- some possible extra entropy sources to the mt_rand method
- a tiny code optimization regarding where to check the $seeded variable (mentioned only so that u check it if i don't introduced a bug).

but i also removed the crypto part because i think modifying it to add my extra entropy sources to it is somewhat bothersome and dangerous (u know how much complex and sensitive cryptography is) and, on the other hand, crypto part is not important for ordinary web applications (or at least for my own project).
but i am not certain about that and will appreciate hearing your advise on this. or u can modify the crypto part yourself; i would appreciate u for doing that and add it to my copy happily.

thank u TerraFrost.
Last edited by wise.man on Fri Mar 23, 2012 12:36 pm, edited 2 times in total.
wise.man
Traveler
 
Posts: 14
Joined: Tue Nov 15, 2011 5:54 pm

Re: Making crypt_random more secure

Postby TerraFrost » Mon Mar 19, 2012 1:52 pm

From wikipedia's entry on CSPRNG's:

A secure block cipher can be converted into a CSPRNG by running it in counter mode. This is done by choosing a random key and encrypting a zero, then encrypting a 1, then encrypting a 2, etc. The counter can also be started at an arbitrary number other than zero. Obviously, the period will be 2n for an n-bit block cipher; equally obviously, the initial values (i.e., key and "plaintext") must not become known to an attacker,however good this CSPRNG construction might be. Otherwise, all security will be lost.

That's the inspiration.
TerraFrost
Legendary Guard
 
Posts: 12357
Joined: Wed Dec 04, 2002 6:37 am

Re: Making crypt_random more secure

Postby wise.man » Tue Mar 20, 2012 5:46 pm

yes i knew why u used that algorithm.
but i think it's not a new and much more secure PRNG (CSPRNG).
because the algorithm inputs are from mt_rand, we must suppose that its security is at most equal to 2^64 bits (mt_rand's seed on 64 bit machines).
we have only 2^64 initial states. all outputs are computable from the initial seed/state, whether we used the stream cipher method or not.
wise.man
Traveler
 
Posts: 14
Joined: Tue Nov 15, 2011 5:54 pm

Re: Making crypt_random more secure

Postby wise.man » Sun Mar 25, 2012 6:36 pm

TerraFrost your help is needed.

i modified crypt_random even more to add the possible extra entropy to other methods (other than mt_rand) too:

Code: Select all
function crypt_random($min = 0, $max = 0x7FFFFFFF)
{
    if ($min == $max) {
        return $min;
    }

   $pee=microtime(); // pee: Possible Extra Entropy
   @$pee.=$_SERVER['REMOTE_ADDR'].$_SERVER['REMOTE_PORT'].$_SERVER['HTTP_USER_AGENT'];
   global $site_key;
   //$site_key is a hard-coded random string (22 chars of upper and lowercase letters and digits) that is specific to each app setup.
   $pee.=$site_key;

    if (function_exists('openssl_random_pseudo_bytes')) {
        // openssl_random_pseudo_bytes() is slow on windows per the following:
        // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
        if ((PHP_OS & "\xDF\xDF\xDF") !== 'WIN') { // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
            //extract(unpack('Nrandom', openssl_random_pseudo_bytes(4)));
            extract(unpack('Nrandom', pack('H*', sha1(openssl_random_pseudo_bytes(4).$pee))));
            return abs($random) % ($max - $min) + $min;
        }
    }

    // see http://en.wikipedia.org/wiki//dev/random
    static $urandom = true;
    if ($urandom === true) {
        // Warning's will be output unles the error suppression operator is used.  Errors such as
        // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
        $urandom = @fopen('/dev/urandom', 'rb');
    }
    if (!is_bool($urandom)) {
        //extract(unpack('Nrandom', fread($urandom, 4)));
        extract(unpack('Nrandom', pack('H*', sha1(fread($urandom, 4).$pee))));
        // say $min = 0 and $max = 3.  if we didn't do abs() then we could have stuff like this:
        // -4 % 3 + 0 = -1, even though -1 < $min
        return abs($random) % ($max - $min) + $min;
    }
   
   
   if(function_exists('mcrypt_create_iv') and version_compare(PHP_VERSION, '5.3.0', '>=')) {
      @$tmp=mcrypt_create_iv(4, MCRYPT_DEV_URANDOM);
      if($tmp!==false) {
         //extract(unpack('Nrandom', $tmp));
         extract(unpack('Nrandom', pack('H*', sha1($tmp.$pee))));
         return abs($random) % ($max - $min) + $min;
      }
   }
   

    /* Prior to PHP 4.2.0, mt_srand() had to be called before mt_rand() could be called.
       Prior to PHP 5.2.6, mt_rand()'s automatic seeding was subpar, as elaborated here:

       http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/

       The seeding routine is pretty much ripped from PHP's own internal GENERATE_SEED() macro:

       http://svn.php.net/viewvc/php/php-src/tags/php_5_3_2/ext/standard/php_rand.h?view=markup */
   static $seeded;
    if (!isset($seeded) and version_compare(PHP_VERSION, '5.2.5', '<=')) {
        $seeded = true;
        mt_srand(fmod(time() * getmypid(), 0x7FFFFFFF) ^ fmod(1000000 * lcg_value(), 0x7FFFFFFF));
    }

    extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF).$pee))));
    return abs($random) % ($max - $min) + $min;

}


please check the code and tell me about your opinion or whether u see any mistakes.
thank u.
wise.man
Traveler
 
Posts: 14
Joined: Tue Nov 15, 2011 5:54 pm

Re: Making crypt_random more secure

Postby TerraFrost » Sun Mar 25, 2012 10:17 pm

Code: Select all
   @$pee.=$_SERVER['REMOTE_ADDR'].$_SERVER['REMOTE_PORT'].$_SERVER['HTTP_USER_AGENT'];

I would do isset() instead of using the error suppression operator. The error suppression operator slows things down a bit and, imho, it's just not as clean.

I guess you could also look at $argv for CLI stuff:

http://php.net/manual/en/reserved.variables.argv.php

For seeding, you could instead just do sha1(serialize($_SERVER) . serialize($_POST) . serialize($_GET) . serialize($_COOKIE) . serialize($_GLOBAL)) or something.
TerraFrost
Legendary Guard
 
Posts: 12357
Joined: Wed Dec 04, 2002 6:37 am

Re: Making crypt_random more secure

Postby TerraFrost » Mon Mar 26, 2012 1:06 am

One thing I really like about phpBB is their dss_rand() function. The problem with mt_srand() is that, by default, the seed is based off of predictable values. phpBB turns that on its head by cascading the entropy. It's not just based off of a bunch of server variables like the time (which is easy to predict), the PID (quoting php.net, "process IDs are not unique, thus they are a weak entropy source. we recommend against relying on pids in security-dependent contexts"), or the output of lcg_value(). Instead it's based on the previous state. The more people visit the site the more the entropy source is mixed up and the harder it is to predict.

Problem with that approach is that phpBB updates it in a MySQL DB. phpseclib doesn't have MySQL as a dependency but we can achieve similar results with PHP sessions. Just fix the session and make it so that session_start() doesn't try to send cookies or cache limiting stuff and viola! Obviously the old cache settings would need to be saved so that crypt_random() doesn't interfere with anything.

Example:

Code: Select all
<?php
//session_id(1);
//session_start();

//$_SESSION['test'] = 'zzz';

// save old session data
$old_session_id = session_id();
$old_use_cookies = ini_get('session.use_cookies');
$old_session_cache_limiter = session_cache_limiter();
if (isset($_SESSION))
{
   $_OLD_SESSION = $_SESSION;
}
if ($old_session_id != '')
{
   session_write_close();
}

// create fixed session to cascade entropy
session_id(2);
ini_set('session.use_cookies', 0);
session_cache_limiter('');
session_start();
$_SESSION['count']++;
echo $_SESSION['count'] . "\r\n";
session_write_close();

// restore old session data
if ($old_session_id != '')
{
   session_id($old_session_id);
   session_start();
   ini_set('session.use_cookies', $old_use_cookies);
   session_cache_limiter($old_session_cache_limiter);
}
else
{
   if (isset($_OLD_SESSION))
   {
      $_SESSION = $_OLD_SESSION;
      unset($_OLD_SESSION);
   }
   else
   {
      unset($_SESSION);
   }
}

print_r($_SESSION);

Works via the CLI as well.

Using $_SESSION seems kinda hackish but I don't know what else could be used. Maybe you could do file_put_contents('/tmp/whatever', $hash) and have it always use /tmp/whatever but that's OS dependent whereas $_SESSION isn't.
TerraFrost
Legendary Guard
 
Posts: 12357
Joined: Wed Dec 04, 2002 6:37 am

Re: Making crypt_random more secure

Postby TerraFrost » Mon Mar 26, 2012 1:07 am

You might want to check this post out too:

viewtopic.php?f=46&t=22285
TerraFrost
Legendary Guard
 
Posts: 12357
Joined: Wed Dec 04, 2002 6:37 am

Re: Making crypt_random more secure

Postby wise.man » Mon Mar 26, 2012 6:19 am

TerraFrost wrote:For seeding, you could instead just do sha1(serialize($_SERVER) . serialize($_POST) . serialize($_GET) . serialize($_COOKIE) . serialize($_GLOBAL)) or something.

looks good.
specially when a user is registering, his personal info, including his password, would contribute to the entropy when generating his login key (i use an automatically generated random string for login key which is used in auto-login cookie).

One thing I really like about phpBB is their dss_rand() function. The problem with mt_srand() is that, by default, the seed is based off of predictable values. phpBB turns that on its head by cascading the entropy. It's not just based off of a bunch of server variables like the time (which is easy to predict), the PID (quoting php.net, "process IDs are not unique, thus they are a weak entropy source. we recommend against relying on pids in security-dependent contexts"), or the output of lcg_value(). Instead it's based on the previous state. The more people visit the site the more the entropy source is mixed up and the harder it is to predict.

thank u for the info.
i have also already had some ideas about gathering entropy from users.
recently i implemented this:
this code is in common.php that is included at the beginning of every site page:
Code: Select all
$cookie=new hm_cookie('reg8log_client_entropy');

$tmp=$cookie->get();

$tmp=sha1($tmp.microtime());

$cookie->set(null, $tmp);

then in crypt_random i use it as another entropy source:
Code: Select all
@ $pee=$site_key.$_SERVER['REMOTE_ADDR']. ... .$_COOKIE['reg8log_client_entropy'];


and i have the idea of adding some entropy gathered by javascript on the client side to the reg8log_client_entropy cookie. e.g. we can collect entropy from user mouse movements, client pc time, screen resolution, etc.
but this is the entropy that helps the user's own security against others.

i also had the idea of creating a common site entropy by gathering entropy from all site users.
this one is useful in general (not only protecting users against each other) and can protect the site/app itself against users.
i think reg8log_client_entropy can be used for this easily.

TerraFrost wrote:You might want to check this post out too:

viewtopic.php?f=46&t=22285

looks sophisticated in logic, but doesn't seem to gather much entropy.
wise.man
Traveler
 
Posts: 14
Joined: Tue Nov 15, 2011 5:54 pm

Re: Making crypt_random more secure

Postby TerraFrost » Mon Mar 26, 2012 12:10 pm

So the session thing was a proof of concept. Here's the POC more fully developed:

Code: Select all
// create fixed session to cascade entropy
session_id(2);
ini_set('session.use_cookies', 0);
session_cache_limiter('');
session_start();
$_SESSION['entropy'] = sha1(serialize($_SERVER) . serialize($_POST) . serialize($_GET) . serialize($_COOKIE) . serialize($_GLOBAL) . serialize($_SESSION) . serialize($_OLD_SESSION))
session_write_close();

And then you use $_SESSION['entropy'] when seeding the CSPRNG.

Because $_SESSION['entropy'] uses itself (serialize($_SESSION)) as a source of entropy it constantly changes even if everything else remains the same.

edit: mcrypt_create_iv could be used with MCRYPT_DEV_URANDOM. don't use MCRYPT_DEV_RANDOM as that'll block. does srand() need to be called on pre-5.3.0?
TerraFrost
Legendary Guard
 
Posts: 12357
Joined: Wed Dec 04, 2002 6:37 am

Re: Making crypt_random more secure

Postby wise.man » Mon Mar 26, 2012 3:17 pm

i don't like the session storage, so i store the entropy in DB.
the security of the sessions is not always reliable specially in shared hosting.
also the session file can be erased (garbage collected) occasionally or by mistakes or maybe other events such as server reboot.
and what about concurrent writes in a single session file by many clients/requests?

----------------------

i removed the client_entropy cookie method for now. probably it isn't seriously needed and enough entropy can be gathered from other parameters (u agree?)

so far i came up with this:

in every site's page i do this:
Code: Select all
$entropy=sha1(microtime().$pepper.$_SERVER['REMOTE_ADDR'].$_SERVER['REMOTE_PORT'].$_SERVER['HTTP_USER_AGENT'].serialize($_POST).serialize($_GET).serialize($_COOKIE));

(note that i have removed some parameters because they seem to generate little/unsecure entropy while wasting resources and degrading performance.)
then i execute this query:
Code: Select all
$query="update `crypto` set `value`=sha1(concat(`value`, '$entropy')) where name='entropy'";

it combines the previous entropy in the db with the new one (and stores the result).
when including crypt_rand i execute this:
Code: Select all
$query="select `value` from `crypto` where `name`='entropy'";

and get the entropy gathered by the system so far.
in crypt_rand i add microtime to each execution in addition to the entropy fetched from the database, to add a possible extra entropy it may create due to execution times noise ($entropy is fetched once per request and is constant during the script lifetime, but microtime can change):
Code: Select all
...sha1(mt_rand(0, 0x7FFFFFFF).$entropy.microtime())

is this scheme good enough?
the entropy gathered on the client side (with javascript) can be added to the parameters if needed, but i think we have enough randomness so far.
wise.man
Traveler
 
Posts: 14
Joined: Tue Nov 15, 2011 5:54 pm

Re: Making crypt_random more secure

Postby TerraFrost » Tue Mar 27, 2012 7:29 am

wise.man wrote:i don't like the session storage, so i store the entropy in DB.
the security of the sessions is not always reliable specially in shared hosting.
also the session file can be erased (garbage collected) occasionally or by mistakes or maybe other events such as server reboot.
and what about concurrent writes in a single session file by many clients/requests?

I think that's a legit concern. My goal with the sessions is to create something that's as good as can be while working in every possible condition. Can't do that with a DB because that's ultra application specific. I figure if you want more (or better) entropy you should rewrite crypt_random() to accommodate your app.

is this scheme good enough?

I think so.

the entropy gathered on the client side (with javascript) can be added to the parameters if needed, but i think we have enough randomness so far.

I agree. More isn't a bad thing, but at the same time, I'm not sure the benefit gained by doing this justifies the time investment that'd be required.
TerraFrost
Legendary Guard
 
Posts: 12357
Joined: Wed Dec 04, 2002 6:37 am

Re: Making crypt_random more secure

Postby wise.man » Mon Apr 23, 2012 4:36 pm

my high security PHP register and login system is presented under GPL.
i used modified crypt_random and implemented the entropy gathering mechanism in it.
also used encryption in several other parts of it with phpseclib.
https://github.com/ferchang/reg8log
Attachments
reg8log.zip
(309.75 KiB) Downloaded 171 times
Last edited by wise.man on Sat Mar 23, 2013 11:21 am, edited 5 times in total.
wise.man
Traveler
 
Posts: 14
Joined: Tue Nov 15, 2011 5:54 pm

Next

Return to phpseclib support

Who is online

Users browsing this forum: No registered users and 1 guest

cron