I need to communicate in PHP with a client that is using the ISAAC stream cipher. As far as I can tell, none of the crypto libraries available for PHP implement this cipher. How could one implement the ISAAC cipher in a PHP application?
(I have found a Java implementation of ISAAC, and have almost succeeded in porting it over to PHP. The only issue is unsigned right shifting in PHP. I wrote a method to do it, but the method fails when the number in the shift is negative.)
The ISAAC cipher is fairly simple, and since it's written in C, it shouldn't be too hard to port it to PHP. The only real complication is that ISAAC uses 32-bit unsigned integers with wrap-around, whereas PHP's integers may be 32 or 64 bits and get auto-converted to floats on overflow. This isn't that big an issue, though, since we can easily work around it by applying bitmasks where needed to force intermediate values down to 32 bits.
Anyway, here's a fairly direct port of the ISAAC reference code to PHP:
As of September 2017, this code is now also available on GitHub.
<?php
/*
------------------------------------------------------------------------------
ISAAC random number generator by Bob Jenkins. PHP port by Ilmari Karonen.
Based on the randport.c and readable.c reference C implementations by Bob
Jenkins, with some inspiration taken from the Perl port by John L. Allen.
This code is released into the public domain. Do whatever you want with it.
HISTORY:
2013-01-20: Initial version. (Typo in version date fixed on 2017-09-19.)
------------------------------------------------------------------------------
*/
class ISAAC {
private $m, $a, $b, $c; // internal state
public $r; // current chunk of results
public function isaac()
{
$c = ++$this->c; // c gets incremented once per 256 results
$b = $this->b += $c; // then combined with b
$a = $this->a;
$m =& $this->m;
$r = array();
for ($i = 0; $i < 256; ++$i) {
$x = $m[$i];
switch ($i & 3) {
case 0: $a ^= ($a << 13); break;
case 1: $a ^= ($a >> 6) & 0x03ffffff; break;
case 2: $a ^= ($a << 2); break;
case 3: $a ^= ($a >> 16) & 0x0000ffff; break;
}
$a += $m[$i ^ 128]; $a &= 0xffffffff;
$m[$i] = $y = ($m[($x >> 2) & 255] + $a + $b) & 0xffffffff;
$r[$i] = $b = ($m[($y >> 10) & 255] + $x) & 0xffffffff;
}
$this->a = $a;
$this->b = $b;
$this->c = $c;
$this->r = $r;
}
public function rand()
{
if (empty($this->r)) $this->isaac();
return array_pop($this->r);
}
private static function mix( &$a, &$b, &$c, &$d, &$e, &$f, &$g, &$h )
{
$a ^= ($b << 11); $d += $a; $b += $c;
$b ^= ($c >> 2) & 0x3fffffff; $e += $b; $c += $d;
$c ^= ($d << 8); $f += $c; $d += $e;
$d ^= ($e >> 16) & 0x0000ffff; $g += $d; $e += $f;
$e ^= ($f << 10); $h += $e; $f += $g;
$f ^= ($g >> 4) & 0x0fffffff; $a += $f; $g += $h;
$g ^= ($h << 8); $b += $g; $h += $a;
$h ^= ($a >> 9) & 0x007fffff; $c += $h; $a += $b;
// 64-bit PHP does something weird on integer overflow; avoid it
$a &= 0xffffffff; $b &= 0xffffffff; $c &= 0xffffffff; $d &= 0xffffffff;
$e &= 0xffffffff; $f &= 0xffffffff; $g &= 0xffffffff; $h &= 0xffffffff;
}
public function __construct ( $seed = null )
{
$this->a = $this->b = $this->c = 0;
$this->m = array_fill(0, 256, 0);
$m =& $this->m;
$a = $b = $c = $d = $e = $f = $g = $h = 0x9e3779b9; // golden ratio
for ($i = 0; $i < 4; ++$i) {
ISAAC::mix($a, $b, $c, $d, $e, $f, $g, $h); // scramble it
}
if ( isset($seed) ) {
if ( is_string($seed) ) {
// emulate casting char* to int* on a little-endian CPU
$seed = array_values(unpack("V256", pack("a1024", $seed)));
}
// initialize using the contents of $seed as the seed
for ($i = 0; $i < 256; $i += 8) {
$a += $seed[$i ]; $b += $seed[$i+1];
$c += $seed[$i+2]; $d += $seed[$i+3];
$e += $seed[$i+4]; $f += $seed[$i+5];
$g += $seed[$i+6]; $h += $seed[$i+7];
ISAAC::mix($a, $b, $c, $d, $e, $f, $g, $h);
$m[$i ] = $a; $m[$i+1] = $b; $m[$i+2] = $c; $m[$i+3] = $d;
$m[$i+4] = $e; $m[$i+5] = $f; $m[$i+6] = $g; $m[$i+7] = $h;
}
// do a second pass to make all of the seed affect all of $m
for ($i = 0; $i < 256; $i += 8) {
$a += $m[$i ]; $b += $m[$i+1]; $c += $m[$i+2]; $d += $m[$i+3];
$e += $m[$i+4]; $f += $m[$i+5]; $g += $m[$i+6]; $h += $m[$i+7];
ISAAC::mix($a, $b, $c, $d, $e, $f, $g, $h);
$m[$i ] = $a; $m[$i+1] = $b; $m[$i+2] = $c; $m[$i+3] = $d;
$m[$i+4] = $e; $m[$i+5] = $f; $m[$i+6] = $g; $m[$i+7] = $h;
}
}
else {
// fill in $m with messy stuff (does anyone really use this?)
for ($i = 0; $i < 256; $i += 8) {
ISAAC::mix($a, $b, $c, $d, $e, $f, $g, $h);
$m[$i ] = $a; $m[$i+1] = $b; $m[$i+2] = $c; $m[$i+3] = $d;
$m[$i+4] = $e; $m[$i+5] = $f; $m[$i+6] = $g; $m[$i+7] = $h;
}
}
// fill in the first set of results
$this->isaac();
}
}
The ISAAC
class constructor takes an optional $seed
parameter, which should be either a 256-element array of 32-bit integers, a string of up to 1024 bytes (which is converted to an array using unpack()
; see the code) or null
(the default). If the seed is null or omitted, the shorter single-pass initialization is used, which corresponds to calling randinit()
with flag == FALSE
in the C reference implementation; otherwise, the two-pass initialization corresponding to flag == TRUE
is used.
The class provides a rand()
method that returns a single 32-bit number, just like the rand()
macro defined in Jenkins' rand.h
. Alternatively, I've left the core isaac()
method and its internal result buffer $r
public, so that those preferring more direct access to the generator can just call isaac()
themselves and grab the output that way. Note that the constructor already calls isaac()
once, just like randinit()
in the reference implementation, so you only need to call it again after exhausting the first 256 output values.
Here are two test programs, the first corresponding to the test code included inside rand.c:
<?php
require_once('isaac.php');
$seed = array_fill(0, 256, 0);
$isaac = new ISAAC ( $seed );
for ($i = 0; $i < 2; ++$i) {
$isaac->isaac(); // XXX: the first output block is dropped!
for ($j = 0; $j < 256; ++$j) {
printf("%08x", $isaac->r[$j]);
if (($j & 7) == 7) echo "\n";
}
}
and the second randtest.c from the ISAAC challenge:
<?php
require_once('isaac.php');
$seed = "This is <i>not</i> the right mytext.";
$isaac = new ISAAC ( $seed );
for ($j = 0; $j < 10 * 256; ++$j) {
printf("%08x ", $isaac->rand());
if (($j & 7) == 7) echo "\n";
}
The outputs of these programs should match randvect.txt and randseed.txt respectively.