I'm newbie in cryptography.
I'm trying to implement CubeHash hashing function on pure PHP.
I've ported some JS-version (32-bit) to PHP (64-bit), but I'm not able to implement CubeHash with param b
when it is grater than 1 (block size is grater than 1 byte).
Please, help me.
run online: https://repl.it/repls/FreshWickedHashfunction
<?php
// repo: https://github.com/windlace/cubehash
// based on https://github.com/RndPhrase/cubehash.js/blob/master/cubehash.js
namespace Cast\Crypto\CubeHash;
// Cubehash 8/1-256 (CubeHash80+8/1+80-256)
//
// CubeHashi+r/b+f-h
//
// http://cubehash.cr.yp.to
// http://en.wikipedia.org/wiki/CubeHash
//
// example-1: https://github.com/RndPhrase/cubehash.js/blob/master/cubehash.js
// example-2: https://github.com/tearsofphoenix/cubehash/blob/master/index.js
function cubehash256($r, $b, $string)
{
return CubeHash256::hash($r, $b, $string);
}
// returns 32-bit representation on 64-bit integer
function i32($value)
{
$value = ($value & 0xFFFFFFFF);
if ($value & 0x80000000) $value = -((~$value & 0xFFFFFFFF) + 1);
return $value;
}
class CubeHash256
{
/**
* Init vector computing by 10r rounds as described in the specification.
*
* @param integer $r The number of rounds per block
* @param integer $b The block size in bytes, defined for {1, 2, 3, ... 128}
* @param integer $h The size of the hash output in bits, defined for {8, 16, 24, 32, ... 512}
*
* @return array
*/
public static function iv($r, $b, $h)
{
$initial_state = array_fill(0, 32, 0);
$initial_state[0] = $h/8;
$initial_state[1] = $b;
$initial_state[2] = $r;
// init state
$state = new \SplFixedArray(32);
for ($i = 0; $i < 32; $i += 1) {
$state[$i] = $initial_state[$i];
}
// finalize (10*r)
for ($i = 0; $i < 10; $i += 1) {
self::transform($r, $state);
}
return $state->toArray();
}
/**
* @param integer $r The number of rounds per block
* @param $b
* @param $data
* @return string
*/
public static function hash($r, $b, $data)
{
// init state
$state = new \SplFixedArray(32);
$iv = self::iv($r, $b, 256);
for ($i = 0; $i < 32; $i += 1) {
$state[$i] = $iv[$i];
}
// update with data
$data .= chr(128);
foreach (str_split($data, $b) as $block) {
$blockHex = unpack('H*', $block);
$blockDec = hexdec($blockHex[1]);
$state[0] ^= $blockDec;
$state = self::transform($r, $state);
}
// finalize
$state[31] ^= 1;
for ($i = 0; $i < 10; $i += 1) {
self::transform($r, $state);
}
// Example for '' (empty string hash)
// $state = [
// -1561800392 => '38d1e8a2',
// -961905875 => '2d7baac6',
// -664644867 => 'fd5262d8',
// -1399003075 => '3de89cac',
// -3546249993 => 'f784a02c',
// -3399252310 => 'aa866335',
// -2373478103 => '29998772',
// -2789414358 => '2aeabc59',
// -1685548184 => '6893889b',
// -3641377267 => '0dfef426',
// -1599741972 => 'ecdfa5a0',
// -3971411240 => 'd8124913',
// -3644175510 => '6a4bca26',
// -2826556663 => '092b8657',
// -854482381 => '33a211cd',
// -3508776574 => '8251dc2e',
// -370904486330 => '46ae5ea4',
// -399992847312 => '308491de',
// -333955865119 => 'e1e5ad3e',
// -367735441682 => 'ee764261',
// -358144429942 => '8ab0ed9c',
// -372630171200 => 'c0d1823d',
// -347016896839 => 'b95e2e34',
// -362137648016 => '7004eaae',
// -386413349251 => '7d36f807',
// -350848758866 => 'aecbc84f',
// -386257376189 => '432c4411',
// -383406607732 => '8c722fbb',
// -360378781490 => 'ce30c017',
// -385614396406 => '0a449737',
// -401364645913 => 'e787cd8c',
// -359536410101 => '0bc2f549',
// ];
// concat hex produces a hash : 38d1e8a22d7baac6fd5262d83de89cacf784a02caa866335299987722aeabc59
// convert to hex
$s = '';
for ($i = 0; $i < 8; $i += 1) {
$s .= self::signed2hex($state[$i], false);
}
return $s;
}
/**
* @param integer $r The number of rounds per block
* @param \SplFixedArray $state The main context
* @return mixed
*/
protected static function transform($r, $state)
{
$y = new \SplFixedArray(16);
for ($ri = 0;$ri < $r; $ri += 1) {
for ($i = 0; $i < 16; $i += 1) $state[$i + 16] += $y[$i^8] = $state[$i];
for ($i = 0; $i < 16; $i += 1) $state[$i] = i32(self::rotate($y[$i], 7)^$state[$i + 16]);
for ($i = 0; $i < 16; $i += 1) $y[$i^2] = $state[$i + 16];
for ($i = 0; $i < 16; $i += 1) $state[$i + 16] = $y[$i] + $state[$i];
for ($i = 0; $i < 16; $i += 1) $y[$i^4] = $state[$i];
for ($i = 0; $i < 16; $i += 1) $state[$i] = i32(self::rotate($y[$i], 11)^$state[$i + 16]);
for ($i = 0; $i < 16; $i += 2) self::swap($state, $i + 16, $i + 17);
}
return $state;
}
protected static function rotate($a, $b)
{
return (0xffffffff00000000 | ($a << $b)) | (($a & 0x00000000ffffffff) >> (32 - $b));
}
protected static function swap($arr, $i, $j)
{
$tmp = $arr[$i];
$arr[$i] = $arr[$j];
$arr[$j] = $tmp;
return $arr;
}
/**
* Converts signed decimal to hex (Two's complement)
*
* @param $value int, signed
*
* @param $reverseEndianness bool, if true reverses the byte order (see machine dependency)
*
* @return string, upper case hex value, both bytes padded left with zeros
*/
protected static function signed2hex($value, $reverseEndianness = true)
{
$packed = pack('i', $value);
$hex='';
for ($i=0; $i < 4; $i++){
$hex .= str_pad( dechex(ord($packed[$i])) , 2, '0', STR_PAD_LEFT);
}
$tmp = str_split($hex, 2);
$out = implode('', ($reverseEndianness ? array_reverse($tmp) : $tmp));
return $out;
}
}
function padBlock($input, $blockSizeBytes)
{
$blockSize = $blockSizeBytes * 2;
$input = bin2hex($input);
$input = str_repeat('0', $blockSize - ((strlen($input) % $blockSize) ?: $blockSize)) . $input;
$input = !strlen($input) ? str_repeat('0', $blockSize) : $input;
return hex2bin($input);
}
function assertEquals($a, $b) {
($a === $b) or
die(
"Equality assertion failed:\n\n" .
"Expected:\n" . (is_array($a) ? 'Array' : $a) . "\n".
"Actual:\n" . (is_array($b) ? 'Array' : $b) . "\n"
);
}
// KNOWN EXAPMLES:
// CubeHash10+1/1+10-256:
assertEquals(
[
-1364746004, 1413475564, -896460311, 1495629273,
831721974, -1114895892, -1131802667, -1345622233,
-966112469, -1678886309, -691894012, 1358586066,
-1854453552, -1070683759, 748270806, 1529972269,
2136745624, 4462657195, 9868789035, 6357691622,
-4981802178, -6422354290, -1400617557, -2741214963,
-272471494, 5329758627, 2868717903, 710720199,
-709654222, 2807896093, 2249717683, 2710321235,
],
CubeHash256::iv(1, 1, 256)
);
assertEquals('80f72e07d04ddadb44a78823e0af2ea9f72ef3bf366fd773aa1fa33fc030e5cb', cubehash256(1, 1, ''));
assertEquals('f63041a946aa98bd47f3175e6009dcb2ccf597b2718617ba46d56f27ffe35d49', cubehash256(1, 1, 'Hello'));
assertEquals('217a4876f2b24cec489c9171f85d53395cc979156ea0254938c4c2c59dfdf8a4', cubehash256(1, 1, 'The quick brown fox jumps over the lazy dog'));
// CubeHash80+8/1+80-256
assertEquals(
[
-2096419883, 658334063, -679114902, 1246757400,
-1523021469, -289996037, 1196718146, 1168084361,
-2027445816, -1170162360, -822837272, 625197683,
1543712850, -1365258909, 759513533, -424228083,
-13765010209, -2824905881, -9887154026, 19352563566,
5669379852, -31581549269, 21359466527, 10648459874,
-8561790247, 9779462261, -22434204802, -4124492433,
19608647852, 9541915967, 5144979599, -4355863926,
],
CubeHash256::iv(8, 1, 256)
);
assertEquals('38d1e8a22d7baac6fd5262d83de89cacf784a02caa866335299987722aeabc59', cubehash256(8, 1, ''));
assertEquals('692638db57760867326f851bd2376533f37b640bd47a0ddc607a9456b692f70f', cubehash256(8, 1, 'Hello'));
assertEquals('94e0c958d85cdfaf554919980f0f50b945b88ad08413e0762d6ff0219aff3e55', cubehash256(8, 1, 'The quick brown fox jumps over the lazy dog'));
// CubeHash160+16/32+160-256
assertEquals(
[
-366226252, -858328417, 1662090865, 893918894,
575745371, -438743453, 2120368433, -187952450,
-1026509162, 1118773360, -797832139, 862050956,
684518564, -1896305277, 1182837760, 1088813995,
7928299971, 5922880469, -36834009791, -21731580295,
-42794932919, 35964212739, -2587323651, -57649962363,
5015516590, -27409035680, -45243252771, 58068387621,
-29703972117, 5548452566, -40318076753, -30769206262,
],
CubeHash256::iv(16, 32, 256)
);
// multybyte-blocks for an empty string works propertly.
assertEquals('44c6de3ac6c73c391bf0906cb7482600ec06b216c7c54a2a8688a6a42676577d', cubehash256(16, 32, ''));
// padding with 0 works propertly
assertEquals('e27007aa498dd2100ffc76ce4eff578e6eb89908967186156f065cf6a61f6855', cubehash256(16, 32, padBlock('', 32)));
// NOT WORKING
assertEquals('e712139e3b892f2f5fe52d0f30d78a0cb16b51b217da0e4acb103dd0856f2db0', cubehash256(16, 32, 'Hello'));
// padding with 0 is NOT WORKING
assertEquals('72070e821ab48ae56cb693c1e59676a9d4c430b0a9321adedea5f00c825c9f69', cubehash256(16, 32, padBlock('Hello', 32)));
// NOT WORKING
assertEquals('3632b64528815b66875e7feb9be68fe3e0e502dd405d7910c23f16e6b6ffeef7', cubehash256(16, 32, hex2bin('8072a313bb0819a9874277b11303e252f3af7a229a407c1a618e8e4f38af6ca3')));
// NOT WORKING
assertEquals('5151e251e348cbbfee46538651c06b138b10eeb71cf6ea6054d7ca5fec82eb79', cubehash256(16, 32, 'The quick brown fox jumps over the lazy dog'));
echo "Done.";
From documentation:
CubeHash maintains a 128-byte state. It xors the first b-byte input block into the first b bytes of the state, transforms the state invertibly through r identical rounds, xors the next b-byte input block into the first b bytes of the state, transforms the state invertibly through r identical rounds, xors the next b-byte input block into the first b bytes of the state, transforms the state invertibly through r identical rounds, etc.
I've solve my issue with multi-byte blocks.
Key features is (from official docs http://cubehash.cr.yp.to):
b
-byte input block into the first b
bytes of the state, transforms the state invertibly through r
identical rounds. Each next block - the same procedure.So we need to:
The next part was modified:
From:
...
/**
* @param integer $r The number of rounds per block
* @param $b
* @param $data
* @return string
*/
public static function hash($r, $b, $data)
{
...
// update with data
$data .= chr(128);
foreach (str_split($data, $b) as $block) {
$blockHex = unpack('H*', $block);
$blockDec = hexdec($blockHex[1]);
$state[0] ^= $blockDec;
$state = self::transform($r, $state);
}
// finalize
$state[31] ^= 1;
...
}
To:
/**
* @param string $value Hex
* @return string
*/
public static function swapEndianness($value)
{
return implode('', array_reverse(str_split($value, 2)));
}
/**
* @param string $value Binary array
* @return false|string
*/
public static function swapEndiannessBin($value)
{
return hex2bin(self::swapEndianness(bin2hex($value)));
}
/**
* @param integer $r The number of rounds per block
* @param $b
* @param $data
* @return string
*/
public static function hash($r, $b, $data)
{
...
// update with data
$data .= chr(128);
$data = self::swapEndiannessBin($data);
$data = padBlock($data, $b);
$data = self::swapEndiannessBin($data);
for ($j = 0; $j < strlen($data); $j += $b) {
$block = substr($data, $j, $b);
$block = bin2hex($block);
$n = 0;
for ($i = 0; $i < strlen($block); $i += 8) {
$byte = substr($block, $i, 8);
$byte = self::swapEndianness($byte);
$state[$n++] ^= hexdec($byte);
}
$state = self::transform($r, $state);
}
// finalize
$state[31] ^= 1;
...
}
The full version is:
<?php
// based on https://github.com/RndPhrase/cubehash.js/blob/master/cubehash.js
namespace Cast\Crypto\CubeHash;
function cubehash256($r, $b, $string)
{
return CubeHash256::hash($r, $b, $string);
}
// returns 32-bit representation on 64-bit integer
function i32($value)
{
$value = ($value & 0xFFFFFFFF);
if ($value & 0x80000000) $value = -((~$value & 0xFFFFFFFF) + 1);
return $value;
}
// Cubehash 8/1-256 (CubeHash80+8/1+80-256)
//
// CubeHashi+r/b+f-h
//
// http://cubehash.cr.yp.to
// http://en.wikipedia.org/wiki/CubeHash
//
// example-1: https://github.com/RndPhrase/cubehash.js/blob/master/cubehash.js
// example-2: https://github.com/tearsofphoenix/cubehash/blob/master/index.js
class CubeHash256
{
/**
* Init vector computing by 10r rounds as described in the specification.
*
* @param integer $r The number of rounds per block
* @param integer $b The block size in bytes, defined for {1, 2, 3, ... 128}
* @param integer $h The size of the hash output in bits, defined for {8, 16, 24, 32, ... 512}
*
* @return array
*/
public static function iv($r, $b, $h)
{
$initial_state = array_fill(0, 32, 0);
$initial_state[0] = $h/8;
$initial_state[1] = $b;
$initial_state[2] = $r;
// init state
$state = new \SplFixedArray(32);
for ($i = 0; $i < 32; $i += 1) {
$state[$i] = $initial_state[$i];
}
// finalize (10*r)
for ($i = 0; $i < 10; $i += 1) {
self::transform($r, $state);
}
return $state->toArray();
}
/**
* @param string $value Hex
* @return string
*/
public static function swapEndianness($value)
{
return implode('', array_reverse(str_split($value, 2)));
}
/**
* @param string $value Binary array
* @return false|string
*/
public static function swapEndiannessBin($value)
{
return hex2bin(self::swapEndianness(bin2hex($value)));
}
/**
* @param integer $r The number of rounds per block
* @param $b
* @param $data
* @return string
*/
public static function hash($r, $b, $data)
{
// init state
$state = new \SplFixedArray(32);
$iv = self::iv($r, $b, 256);
for ($i = 0; $i < 32; $i += 1) {
$state[$i] = $iv[$i];
}
// update with data
$data .= chr(128);
$data = self::swapEndiannessBin($data);
$data = padBlock($data, $b);
$data = self::swapEndiannessBin($data);
for ($j = 0; $j < strlen($data); $j += $b) {
$block = substr($data, $j, $b);
$block = bin2hex($block);
$n = 0;
for ($i = 0; $i < strlen($block); $i += 8) {
$byte = substr($block, $i, 8);
$byte = self::swapEndianness($byte);
$state[$n++] ^= hexdec($byte);
}
$state = self::transform($r, $state);
}
// finalize
$state[31] ^= 1;
for ($i = 0; $i < 10; $i += 1) {
self::transform($r, $state);
}
// Example for '' (empty string hash)
// $state = [
// -1561800392 => '38d1e8a2',
// -961905875 => '2d7baac6',
// -664644867 => 'fd5262d8',
// -1399003075 => '3de89cac',
// -3546249993 => 'f784a02c',
// -3399252310 => 'aa866335',
// -2373478103 => '29998772',
// -2789414358 => '2aeabc59',
// -1685548184 => '6893889b',
// -3641377267 => '0dfef426',
// -1599741972 => 'ecdfa5a0',
// -3971411240 => 'd8124913',
// -3644175510 => '6a4bca26',
// -2826556663 => '092b8657',
// -854482381 => '33a211cd',
// -3508776574 => '8251dc2e',
// -370904486330 => '46ae5ea4',
// -399992847312 => '308491de',
// -333955865119 => 'e1e5ad3e',
// -367735441682 => 'ee764261',
// -358144429942 => '8ab0ed9c',
// -372630171200 => 'c0d1823d',
// -347016896839 => 'b95e2e34',
// -362137648016 => '7004eaae',
// -386413349251 => '7d36f807',
// -350848758866 => 'aecbc84f',
// -386257376189 => '432c4411',
// -383406607732 => '8c722fbb',
// -360378781490 => 'ce30c017',
// -385614396406 => '0a449737',
// -401364645913 => 'e787cd8c',
// -359536410101 => '0bc2f549',
// ];
// concat hex produces a hash : 38d1e8a22d7baac6fd5262d83de89cacf784a02caa866335299987722aeabc59
// convert to hex
$s = '';
for ($i = 0; $i < 8; $i += 1) {
$s .= self::signed2hex($state[$i], false);
}
return $s;
}
/**
* @param integer $r The number of rounds per block
* @param \SplFixedArray $state The main context
* @return mixed
*/
protected static function transform($r, $state)
{
$y = new \SplFixedArray(16);
for ($ri = 0;$ri < $r; $ri += 1) {
for ($i = 0; $i < 16; $i += 1) $state[$i + 16] += $y[$i^8] = $state[$i];
for ($i = 0; $i < 16; $i += 1) $state[$i] = i32(self::rotate($y[$i], 7)^$state[$i + 16]);
for ($i = 0; $i < 16; $i += 1) $y[$i^2] = $state[$i + 16];
for ($i = 0; $i < 16; $i += 1) $state[$i + 16] = $y[$i] + $state[$i];
for ($i = 0; $i < 16; $i += 1) $y[$i^4] = $state[$i];
for ($i = 0; $i < 16; $i += 1) $state[$i] = i32(self::rotate($y[$i], 11)^$state[$i + 16]);
for ($i = 0; $i < 16; $i += 2) self::swap($state, $i + 16, $i + 17);
}
return $state;
}
protected static function rotate($a, $b)
{
return (0xffffffff00000000 | ($a << $b)) | (($a & 0x00000000ffffffff) >> (32 - $b));
}
protected static function swap($arr, $i, $j)
{
$tmp = $arr[$i];
$arr[$i] = $arr[$j];
$arr[$j] = $tmp;
return $arr;
}
/**
* Converts signed decimal to hex (Two's complement)
*
* @param $value int, signed
*
* @param $reverseEndianness bool, if true reverses the byte order (see machine dependency)
*
* @return string, upper case hex value, both bytes padded left with zeros
*/
protected static function signed2hex($value, $reverseEndianness = true)
{
$packed = pack('i', $value);
$hex='';
for ($i=0; $i < 4; $i++){
$hex .= str_pad( dechex(ord($packed[$i])) , 2, '0', STR_PAD_LEFT);
}
return $reverseEndianness ? self::swapEndianness($hex) : $hex;
}
}
function padBlock($input, $blockSizeBytes)
{
$blockSize = $blockSizeBytes * 2;
$input = bin2hex($input);
$input = str_repeat('0', $blockSize - ((strlen($input) % $blockSize) ?: $blockSize)) . $input;
$input = !strlen($input) ? str_repeat('0', $blockSize) : $input;
return hex2bin($input);
}
function assertEquals($a, $b) {
($a === $b) or
die(
"Equality assertion failed:\n\n" .
"Expected:\n" . (is_array($a) ? 'Array' : $a) . "\n".
"Actual:\n" . (is_array($b) ? 'Array' : $b) . "\n"
);
}
// KNOWN EXAPMLES:
// CubeHash10+1/1+10-256:
assertEquals(
[
-1364746004, 1413475564, -896460311, 1495629273,
831721974, -1114895892, -1131802667, -1345622233,
-966112469, -1678886309, -691894012, 1358586066,
-1854453552, -1070683759, 748270806, 1529972269,
2136745624, 4462657195, 9868789035, 6357691622,
-4981802178, -6422354290, -1400617557, -2741214963,
-272471494, 5329758627, 2868717903, 710720199,
-709654222, 2807896093, 2249717683, 2710321235,
],
CubeHash256::iv(1, 1, 256)
);
assertEquals('80f72e07d04ddadb44a78823e0af2ea9f72ef3bf366fd773aa1fa33fc030e5cb', cubehash256(1, 1, ''));
assertEquals('f63041a946aa98bd47f3175e6009dcb2ccf597b2718617ba46d56f27ffe35d49', cubehash256(1, 1, 'Hello'));
assertEquals('217a4876f2b24cec489c9171f85d53395cc979156ea0254938c4c2c59dfdf8a4', cubehash256(1, 1, 'The quick brown fox jumps over the lazy dog'));
// CubeHash80+8/1+80-256
assertEquals(
[
-2096419883, 658334063, -679114902, 1246757400,
-1523021469, -289996037, 1196718146, 1168084361,
-2027445816, -1170162360, -822837272, 625197683,
1543712850, -1365258909, 759513533, -424228083,
-13765010209, -2824905881, -9887154026, 19352563566,
5669379852, -31581549269, 21359466527, 10648459874,
-8561790247, 9779462261, -22434204802, -4124492433,
19608647852, 9541915967, 5144979599, -4355863926,
],
CubeHash256::iv(8, 1, 256)
);
assertEquals('38d1e8a22d7baac6fd5262d83de89cacf784a02caa866335299987722aeabc59', cubehash256(8, 1, ''));
assertEquals('692638db57760867326f851bd2376533f37b640bd47a0ddc607a9456b692f70f', cubehash256(8, 1, 'Hello'));
assertEquals('94e0c958d85cdfaf554919980f0f50b945b88ad08413e0762d6ff0219aff3e55', cubehash256(8, 1, 'The quick brown fox jumps over the lazy dog'));
// CubeHash160+16/32+160-256
assertEquals(
[
-366226252, -858328417, 1662090865, 893918894,
575745371, -438743453, 2120368433, -187952450,
-1026509162, 1118773360, -797832139, 862050956,
684518564, -1896305277, 1182837760, 1088813995,
7928299971, 5922880469, -36834009791, -21731580295,
-42794932919, 35964212739, -2587323651, -57649962363,
5015516590, -27409035680, -45243252771, 58068387621,
-29703972117, 5548452566, -40318076753, -30769206262,
],
CubeHash256::iv(16, 32, 256)
);
// multybyte-blocks for an empty string works propertly.
assertEquals('44c6de3ac6c73c391bf0906cb7482600ec06b216c7c54a2a8688a6a42676577d', cubehash256(16, 32, ''));
// padding with 0 works propertly
assertEquals('e27007aa498dd2100ffc76ce4eff578e6eb89908967186156f065cf6a61f6855', cubehash256(16, 32, padBlock('', 32)));
// WORKING
assertEquals('e712139e3b892f2f5fe52d0f30d78a0cb16b51b217da0e4acb103dd0856f2db0', cubehash256(16, 32, 'Hello'));
// padding with 0 is WORKING
assertEquals('72070e821ab48ae56cb693c1e59676a9d4c430b0a9321adedea5f00c825c9f69', cubehash256(16, 32, padBlock('Hello', 32)));
// WORKING
assertEquals('3632b64528815b66875e7feb9be68fe3e0e502dd405d7910c23f16e6b6ffeef7', cubehash256(16, 32, hex2bin('8072a313bb0819a9874277b11303e252f3af7a229a407c1a618e8e4f38af6ca3')));
// WORKING
assertEquals('5151e251e348cbbfee46538651c06b138b10eeb71cf6ea6054d7ca5fec82eb79', cubehash256(16, 32, 'The quick brown fox jumps over the lazy dog'));
echo "Done.";
Run online: https://repl.it/repls/AwesomeWorldlyWebsite
Packagist: https://packagist.org/packages/cast/cubehash