<?
/**
* Blame Scott Arciszewski of Paragon Initiative Enterprises for this atrocity.
*
* https://paragonie.com
*/
class DeterministicRandom
{
protected $counter;
public function __construct(string $seed = '', int $counter = 0)
{
$this->seed('set', $seed);
$this->counter = 0;
}
private function seed(string $action = 'get', string $data = '')
{
static $seed = null;
if ($action === 'set') {
$seed = $data;
} elseif ($action === 'get') {
return $data;
} else {
throw new \Error(
'Unknown action'
);
}
}
public function getBytes(int $numBytes): string
{
return \openssl_encrypt(
\str_repeat("\0", $numBytes),
'aes-128-ctr',
$this->seed('get'),
OPENSSL_RAW_DATA,
$this->getNonce($numBytes)
);
}
public function getInt(int $min, int $max): int
{
/**
* Now that we've verified our weak typing system has given us an integer,
* let's validate the logic then we can move forward with generating random
* integers along a given range.
*/
if ($min > $max) {
throw new Error(
'Minimum value must be less than or equal to the maximum value'
);
}
if ($max === $min) {
return $min;
}
/**
* Initialize variables to 0
*
* We want to store:
* $bytes => the number of random bytes we need
* $mask => an integer bitmask (for use with the &) operator
* so we can minimize the number of discards
*/
$attempts = $bits = $bytes = $mask = $valueShift = 0;
/**
* At this point, $range is a positive number greater than 0. It might
* overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
* a float and we will lose some precision.
*/
$range = $max - $min;
/**
* Test for integer overflow:
*/
if (!is_int($range)) {
/**
* Still safely calculate wider ranges.
* Provided by @CodesInChaos, @oittaa
*
* @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
*
* We use ~0 as a mask in this case because it generates all 1s
*
* @ref https://eval.in/400356 (32-bit)
* @ref http://3v4l.org/XX9r5 (64-bit)
*/
$bytes = PHP_INT_SIZE;
$mask = ~0;
} else {
/**
* $bits is effectively ceil(log($range, 2)) without dealing with
* type juggling
*/
while ($range > 0) {
if ($bits % 8 === 0) {
++$bytes;
}
++$bits;
$range >>= 1;
$mask = $mask << 1 | 1;
}
$valueShift = $min;
}
/**
* Now that we have our parameters set up, let's begin generating
* random integers until one falls between $min and $max
*/
do {
/**
* The rejection probability is at most 0.5, so this corresponds
* to a failure probability of 2^-128 for a working RNG
*/
if ($attempts > 128) {
throw new Exception(
'random_int: RNG is broken - too many rejections'
);
}
/**
* Let's grab the necessary number of random bytes
*/
$randomByteString = $this->getBytes($bytes);
if ($randomByteString === false) {
throw new Exception(
'Random number generator failure'
);
}
/**
* Let's turn $randomByteString into an integer
*
* This uses bitwise operators (<< and |) to build an integer
* out of the values extracted from ord()
*
* Example: [9F] | [6D] | [32] | [0C] =>
* 159 + 27904 + 3276800 + 201326592 =>
* 204631455
*/
$val = 0;
for ($i = 0; $i < $bytes; ++$i) {
$val |= ord($randomByteString[$i]) << ($i * 8);
}
/**
* Apply mask
*/
$val &= $mask;
$val += $valueShift;
++$attempts;
/**
* If $val overflows to a floating point number,
* ... or is larger than $max,
* ... or smaller than $min,
* then try again.
*/
} while (!is_int($val) || $val > $max || $val < $min);
return (int) $val;
}
protected function getNonce(int $increment = 0): string
{
$nonce = '';
$ctr = $this->counter;
while ($ctr > 0) {
$nonce = \chr($ctr & 0xFF) . $nonce;
$ctr >>= 8;
}
$this->counter += $increment;
return \str_pad($nonce, 16, "\0", STR_PAD_LEFT);
}
}
$seed = str_repeat("\x80", 16);
$rnd1 = new DeterministicRandom($seed);
$rnd2 = new DeterministicRandom($seed);
$out1 = $rnd1->getBytes(16);
$out2 = $rnd1->getBytes(16);
$out3 = $rnd2->getBytes(32);
$int1 = $rnd1->getInt(0, 255);
$int2 = $rnd2->getInt(0, 255);
var_dump([
bin2hex($out1),
bin2hex($out2),
bin2hex($out3),
$int1,
$int2
]);
<?
/**
* Blame Scott Arciszewski of Paragon Initiative Enterprises for this atrocity.
*
* https://paragonie.com
*/
class DeterministicRandom
{
protected $counter;
public function __construct(string $seed = '', int $counter = 0)
{
$this->seed('set', $seed);
$this->counter = 0;
}
private function seed(string $action = 'get', string $data = '')
{
static $seed = null;
if ($action === 'set') {
$seed = $data;
} elseif ($action === 'get') {
return $data;
} else {
throw new \Error(
'Unknown action'
);
}
}
public function getBytes(int $numBytes): string
{
return \openssl_encrypt(
\str_repeat("\0", $numBytes),
'aes-128-ctr',
$this->seed('get'),
OPENSSL_RAW_DATA,
$this->getNonce($numBytes)
);
}
public function getInt(int $min, int $max): int
{
/**
* Now that we've verified our weak typing system has given us an integer,
* let's validate the logic then we can move forward with generating random
* integers along a given range.
*/
if ($min > $max) {
throw new Error(
'Minimum value must be less than or equal to the maximum value'
);
}
if ($max === $min) {
return $min;
}
/**
* Initialize variables to 0
*
* We want to store:
* $bytes => the number of random bytes we need
* $mask => an integer bitmask (for use with the &) operator
* so we can minimize the number of discards
*/
$attempts = $bits = $bytes = $mask = $valueShift = 0;
/**
* At this point, $range is a positive number greater than 0. It might
* overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
* a float and we will lose some precision.
*/
$range = $max - $min;
/**
* Test for integer overflow:
*/
if (!is_int($range)) {
/**
* Still safely calculate wider ranges.
* Provided by @CodesInChaos, @oittaa
*
* @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
*
* We use ~0 as a mask in this case because it generates all 1s
*
* @ref https://eval.in/400356 (32-bit)
* @ref http://3v4l.org/XX9r5 (64-bit)
*/
$bytes = PHP_INT_SIZE;
$mask = ~0;
} else {
/**
* $bits is effectively ceil(log($range, 2)) without dealing with
* type juggling
*/
while ($range > 0) {
if ($bits % 8 === 0) {
++$bytes;
}
++$bits;
$range >>= 1;
$mask = $mask << 1 | 1;
}
$valueShift = $min;
}
/**
* Now that we have our parameters set up, let's begin generating
* random integers until one falls between $min and $max
*/
do {
/**
* The rejection probability is at most 0.5, so this corresponds
* to a failure probability of 2^-128 for a working RNG
*/
if ($attempts > 128) {
throw new Exception(
'random_int: RNG is broken - too many rejections'
);
}
/**
* Let's grab the necessary number of random bytes
*/
$randomByteString = $this->getBytes($bytes);
if ($randomByteString === false) {
throw new Exception(
'Random number generator failure'
);
}
/**
* Let's turn $randomByteString into an integer
*
* This uses bitwise operators (<< and |) to build an integer
* out of the values extracted from ord()
*
* Example: [9F] | [6D] | [32] | [0C] =>
* 159 + 27904 + 3276800 + 201326592 =>
* 204631455
*/
$val = 0;
for ($i = 0; $i < $bytes; ++$i) {
$val |= ord($randomByteString[$i]) << ($i * 8);
}
/**
* Apply mask
*/
$val &= $mask;
$val += $valueShift;
++$attempts;
/**
* If $val overflows to a floating point number,
* ... or is larger than $max,
* ... or smaller than $min,
* then try again.
*/
} while (!is_int($val) || $val > $max || $val < $min);
return (int) $val;
}
protected function getNonce(int $increment = 0): string
{
$nonce = '';
$ctr = $this->counter;
while ($ctr > 0) {
$nonce = \chr($ctr & 0xFF) . $nonce;
$ctr >>= 8;
}
$this->counter += $increment;
return \str_pad($nonce, 16, "\0", STR_PAD_LEFT);
}
}
$seed = str_repeat("\x80", 16);
$rnd1 = new DeterministicRandom($seed);
$rnd2 = new DeterministicRandom($seed);
$out1 = $rnd1->getBytes(16);
$out2 = $rnd1->getBytes(16);
$out3 = $rnd2->getBytes(32);
$int1 = $rnd1->getInt(0, 255);
$int2 = $rnd2->getInt(0, 255);
var_dump([
bin2hex($out1),
bin2hex($out2),
bin2hex($out3),
$int1,
$int2
]);
Output for 8.3.5
Warning: PHP Startup: Unable to load dynamic library 'sodium.so' (tried: /usr/lib/php/8.3.5/modules/sodium.so (libsodium.so.23: cannot open shared object file: No such file or directory), /usr/lib/php/8.3.5/modules/sodium.so.so (/usr/lib/php/8.3.5/modules/sodium.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
<?
/**
* Blame Scott Arciszewski of Paragon Initiative Enterprises for this atrocity.
*
* https://paragonie.com
*/
class DeterministicRandom
{
protected $counter;
public function __construct(string $seed = '', int $counter = 0)
{
$this->seed('set', $seed);
$this->counter = 0;
}
private function seed(string $action = 'get', string $data = '')
{
static $seed = null;
if ($action === 'set') {
$seed = $data;
} elseif ($action === 'get') {
return $data;
} else {
throw new \Error(
'Unknown action'
);
}
}
public function getBytes(int $numBytes): string
{
return \openssl_encrypt(
\str_repeat("\0", $numBytes),
'aes-128-ctr',
$this->seed('get'),
OPENSSL_RAW_DATA,
$this->getNonce($numBytes)
);
}
public function getInt(int $min, int $max): int
{
/**
* Now that we've verified our weak typing system has given us an integer,
* let's validate the logic then we can move forward with generating random
* integers along a given range.
*/
if ($min > $max) {
throw new Error(
'Minimum value must be less than or equal to the maximum value'
);
}
if ($max === $min) {
return $min;
}
/**
* Initialize variables to 0
*
* We want to store:
* $bytes => the number of random bytes we need
* $mask => an integer bitmask (for use with the &) operator
* so we can minimize the number of discards
*/
$attempts = $bits = $bytes = $mask = $valueShift = 0;
/**
* At this point, $range is a positive number greater than 0. It might
* overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
* a float and we will lose some precision.
*/
$range = $max - $min;
/**
* Test for integer overflow:
*/
if (!is_int($range)) {
/**
* Still safely calculate wider ranges.
* Provided by @CodesInChaos, @oittaa
*
* @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
*
* We use ~0 as a mask in this case because it generates all 1s
*
* @ref https://eval.in/400356 (32-bit)
* @ref http://3v4l.org/XX9r5 (64-bit)
*/
$bytes = PHP_INT_SIZE;
$mask = ~0;
} else {
/**
* $bits is effectively ceil(log($range, 2)) without dealing with
* type juggling
*/
while ($range > 0) {
if ($bits % 8 === 0) {
++$bytes;
}
++$bits;
$range >>= 1;
$mask = $mask << 1 | 1;
}
$valueShift = $min;
}
/**
* Now that we have our parameters set up, let's begin generating
* random integers until one falls between $min and $max
*/
do {
/**
* The rejection probability is at most 0.5, so this corresponds
* to a failure probability of 2^-128 for a working RNG
*/
if ($attempts > 128) {
throw new Exception(
'random_int: RNG is broken - too many rejections'
);
}
/**
* Let's grab the necessary number of random bytes
*/
$randomByteString = $this->getBytes($bytes);
if ($randomByteString === false) {
throw new Exception(
'Random number generator failure'
);
}
/**
* Let's turn $randomByteString into an integer
*
* This uses bitwise operators (<< and |) to build an integer
* out of the values extracted from ord()
*
* Example: [9F] | [6D] | [32] | [0C] =>
* 159 + 27904 + 3276800 + 201326592 =>
* 204631455
*/
$val = 0;
for ($i = 0; $i < $bytes; ++$i) {
$val |= ord($randomByteString[$i]) << ($i * 8);
}
/**
* Apply mask
*/
$val &= $mask;
$val += $valueShift;
++$attempts;
/**
* If $val overflows to a floating point number,
* ... or is larger than $max,
* ... or smaller than $min,
* then try again.
*/
} while (!is_int($val) || $val > $max || $val < $min);
return (int) $val;
}
protected function getNonce(int $increment = 0): string
{
$nonce = '';
$ctr = $this->counter;
while ($ctr > 0) {
$nonce = \chr($ctr & 0xFF) . $nonce;
$ctr >>= 8;
}
$this->counter += $increment;
return \str_pad($nonce, 16, "\0", STR_PAD_LEFT);
}
}
$seed = str_repeat("\x80", 16);
$rnd1 = new DeterministicRandom($seed);
$rnd2 = new DeterministicRandom($seed);
$out1 = $rnd1->getBytes(16);
$out2 = $rnd1->getBytes(16);
$out3 = $rnd2->getBytes(32);
$int1 = $rnd1->getInt(0, 255);
$int2 = $rnd2->getInt(0, 255);
var_dump([
bin2hex($out1),
bin2hex($out2),
bin2hex($out3),
$int1,
$int2
]);
Output for 7.1.20, 7.2.6, 7.3.32 - 7.3.33, 7.4.33, 8.0.13
Fatal error: Uncaught Error: Call to undefined function openssl_encrypt() in /in/FQV94:34
Stack trace:
#0 /in/FQV94(184): DeterministicRandom->getBytes(16)
#1 {main}
thrown in /in/FQV94 on line 34
Process exited with code 255.