function urandom_rand($min = 0, $max = 0x7FFFFFFF)
{
$min = (int)$min;
$max = (int)$max;
if ($max <= $min)
trigger_error('Minimum value must be greater than maximum value.');
if ($min < 0 || $max > 0x7FFFFFFF)
trigger_error('Values must be between 0 and 2147483647.');
$M = bcadd(0x7FFFFFFF,1); // (up bound of iv)+1
$N = bcadd($max-$min, 1); // how many different values this function can return
$h = bcmod($M, $N); // the last h integers from unpack are "invalids"
do
{
$bytes = mcrypt_create_iv(4, MCRYPT_DEV_URANDOM);
$r = unpack("N", $bytes)[1] & 0x7FFFFFFF;
} while ($r > ($M-$h));
return (bcmod($r, $N) + $min);
}
Valid values for $bytes
are
0x00000000...0xffffffff
After the unpack()
, every value from $bytes
is matching exactly one value of:
-0x80000000..0x00000000..0x7ffffffff
So, perfect-equiprobability. After bitwise &
, every value from unpack()
matches exactly two values of:
0x00000000..0x7ffffffff
Now, we cut that interval into S
sub-intervals of [KN..(K+1)N[
(S>=1 because $max<=0x7fffffff) plus one remaining interval I
. Then, we map each [KN..(K+1)N[
to [0..N-1]
.
So far, each value from [0..N-1]
has exactly S
ways to be picked.
Now, let's deal with the remaining interval I
(might be an empty interval). If $r
is in that interval, then we cannot do anything with it: we cannot easily match elements from I
with elements from [0..N-1]
because their size mismatch (I
has less elements). We could group elements from [0..N-1]
and then match these elements with I
but that's far too complex for the earn.
So, if $r
is in that remaining I
, easy way is just to pick another $r
.
Since I
has h = (0x7fffffff+1)%N
elements, that's why we keep picking random values as long as $r > (0x7fffffff+1)-h
.
Only problem you can have is that the function urandom_rand()
may take anytime to execute, since it loops until it luckily gets a $r
outside I
.
Here is one scenario of exploiting the inequiprobability problem:
Let's say you pick a random number out of {0,1,...14}
, expecting a probability of p(0)=P(1)=...=p(14)=1/15=6.67%
. People can bet on a number, and they will get 14.5x
their bet if they win (the 0.5x
remaining is your commission).
If you actually have p(0)=7.03%
and p(1)=..=p(14)=6.64%
then one can bet on 0 and expect 7.03% chances to win 14.5x their bet. So they averagely win 7.03%*14.5=1.01935
. Since it's greater than 1, you can keep betting forever and be a winner, despite the casino's commission.
There are probably similar betting game applied to cryptography.
Last correction to my comment about ceil(log(N,k))
(I was tired yesterday night): correct formula is
floor(log(N,k)+1)
gives the number of digits of N
in base k
for N integer and N>=1
Without using bc function:
function urandom_rand($min = 0, $max = 0x7FFFFFFF)
{
$min = (int)$min;
$max = (int)$max;
if ($max <= $min)
trigger_error('Minimum value must be greater than maximum value.');
if ($min < 0 || $max > 0x7FFFFFFF)
trigger_error('Values must be between 0 and 2147483647.');
// Decrease everything from 1st version by $N to avoid INT overflow
// if min == 0 and max == 0x7fffffff then these M,N,h will overflow
// BUT in such case, MNh won't be used (see loop below)
// 1 <= $max - $min <= 0x7FFFFFFE
$N = ($max - $min) + 1; // 2 <= $N <= 0x7FFFFFFF
$M = (0x7FFFFFFF - $N) + 1; // 1 <= $M <= 0x7FFFFFFE
$h = $M % $N; // 0 <= $h <= $N-1 <= 0x7FFFFFFE
do
{
$bytes = mcrypt_create_iv(4, MCRYPT_DEV_URANDOM);
$r = unpack("N", $bytes)[1] & 0x7FFFFFFF;
if ($min == 0 && $max == 0x7FFFFFFF)
return $r; // direct corresponding
} while (($r - $N) > ($M - $h));
return (($r%$N)+$min);
}
$max - $min
when calculating$ivSize
and than basically add$min
? \$\endgroup\$$min=0, $max=14
then$s = (($r%15)+15)%15
. Since$r>0
then+15)%15
is useless, so$s = $r%15
. For$r in {0..254}
,$s is in {0..14}
and for$r=255
(the$r
max),$s=0
. The value 0 has one more chance to appear: p(0)=18/256=7.03% when others have p(1,..,14)=17/256
=6.64%. Output is not equiprobable. Not sure how it can impact the security. \$\endgroup\$$ivSize = ceil(log($max-$min, 0x100));
instead.ceil(log(N,k))
counts how many digitN
has in basek
(byte is a 256-base where digits are 0..255) \$\endgroup\$