Converting byte input to a decimal?

Discussions of secure PHP coding. Security in software is important, so don't be afraid to ask. And when answering: be anal. Nitpick. No security vulnerability is too small.

Moderator: General Moderators

Post Reply
User avatar
The Phoenix
Forum Contributor
Posts: 294
Joined: Fri Oct 06, 2006 8:12 pm

Converting byte input to a decimal?

Post by The Phoenix »

I have gathered three different functions for pulling random values from sources (mcrypt, openssl, and /dev/urandom). Each has a different way of 'fitting' byte inputs into a decimal range. For the purpose of being a drop-in replacement for mt_rand (but with 'more' random input), which is the most correct method for fitting, and why?

Code: Select all

function openssl_rand ($min = 0, $max = 0x7FFFFFFF)
{
    $range = $max - $min;
    if ($range == 0)
    {
        return false; // Not so random...
    }
    $log = log ($range, 2);
    $bytes = (int) ($log / 8 ) + 1; // Length in bytes
    $bits = (int) $log + 1; // Length in bits
    $filter = (int) (1 << $bits) - 1; // Set all lower bits to 1
    do
    {
        $rnd = hexdec (bin2hex (openssl_random_pseudo_bytes ($bytes, $s)));
        $rnd = $rnd & $filter; // Discard irrelevant bits
    }
    while ($rnd >= $range);
    return $min + $rnd;
}

function mcrypt_rand ($min = 0, $max = 0x7FFFFFFF)
{
    $range = $max - $min;
    if ($range < 0 || $range > 0x7FFFFFFF)
    {
        throw new RuntimeException ("Bad range");
    }
    $bytes = mcrypt_create_iv (4, MCRYPT_DEV_URANDOM);
    if ($bytes === false || strlen($bytes) != 4)
    {
        throw new RuntimeException ("Unable to get 4 bytes");
    }
    $ary = unpack ("Nint", $bytes);
    $val = $ary['int'] & 0x7FFFFFFF;   // 32-bit safe
    $fp = (float) $val / 2147483647.0; // Convert to [0 , 1]
    return round ($fp * $range) + $min;
}

function u_rand ($min = 0, $max = 0x7FFFFFFF)
{
    $bits = '';
    $range = $max - $min;
    $bytes = ceil ($range / 256);
    $fp = @fopen ('/dev/urandom', 'rb');
    if ($fp !== FALSE)
    {
        $bits .= @fread ($fp, $bytes);
        @fclose ($fp);
    }
    $bitlength = strlen ($bits);
    for ($i = 0; $i < $bitlength; $i++)
    {
        $int =  1 + (ord ($bits[$i]) % (($max - $min) + 1));
    }
    return $int;
}
User avatar
mecha_godzilla
Forum Contributor
Posts: 375
Joined: Wed Apr 14, 2010 4:45 pm
Location: UK

Re: Converting byte input to a decimal?

Post by mecha_godzilla »

Hi,

Given that the random data you're working with is binary in nature, would it make sense to use some kind of bitwise operation against the maximum value that you want to use? To get the correct input size you might still need to truncate, convert or average larger chunks of data to fit smaller ones though - the limit you're working against is essentially dictated by the maximum decimal value that PHP will support if you need a drop-in replacement for mt_rand. I use a PBKDF2 implementation in some of my applications for key creation, so you might want to also take a look at something like that to see how random starting data can be randomised further to generate a cryptographically secure binary string of the correct size which you can then use in a bitwise operation (plus, these kind of key generation routines tend to run very fast so there's no reason why you can't iterate through the random starting data hundreds or thousands of times).

Also, where do mcrypt and openssl get their random values from? I realise that mt_rand is not secure enough for your needs, but if you can find out how mcrypt and openssl are implemented then this might help narrow down the options. If your implementation will be used in a commercial application or distributed to other users, it would presumably be better if you didn't rely on /dev/urandom - there seem to be a lot more people running WAMP servers these days than there used to be, so you might want to include a fallback implementation.

Hopefully that's of some use anyway :mrgreen:

HTH,

Mecha Godzilla
Post Reply