I'm trying to "translate" the following function written in C in PHP. The problem is, the results returned by the function written in PHP are different from those returned by the one in C.
I think the problem comes from integer overflows in PHP which is not able to treat variables as unsigned 32-bit integer.
I tried to AND-mask all operation with 0xFFFFFFFF without success. Have you got other solutions that could work?
The C function:
void
decipher(const uint32_t num_rounds, uint32_t v[2], const uint32_t key[4])
{
uint32_t idx;
uint32_t v0 = v[0];
uint32_t v1 = v[1];
uint32_t delta = 0x9E3779B9;
uint32_t sum = delta * num_rounds;
for (idx = 0; idx < num_rounds; ++idx)
{
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0] = v0;
v[1] = v1;
}
The PHP function:
function decipher($num_rounds, &$v, $key)
{
$v0 = $v[0];
$v1 = $v[1];
$delta = 0x9E3779B9;
$sum = ($delta * $num_rounds) & 0xFFFFFFFF;
for ($idx = 0; $idx < $num_rounds; ++$idx)
{
$v1 -= (((($v0 << 4) ^ ($v0 >> 5)) + $v0) ^ ($sum + $key[($sum >> 11) & 3])) & 0xFFFFFFFF;
$v1 &= 0xFFFFFFFF;
$sum -= $delta;
$sum &= 0xFFFFFFFF;
$v0 -= ((($v1 << 4) ^ ($v1 >> 5) + $v1) ^ ($sum + $key[$sum & 3])) & 0xFFFFFFFF;
$v0 &= 0xFFFFFFFF;
}
$v[0] = $v0;
$v[1] = $v1;
}
Thank you.
I found a solution: the following code uses functions which allows to do shift operations and additions on unsigned 32-bit integers.
function decipher($num_rounds, &$v, $key)
{
$v0 = $v[0];
$v1 = $v[1];
$delta = 0x9E3779B9;
$sum = ($delta * $num_rounds) & 0xFFFFFFFF;
for ($idx = 0; $idx < $num_rounds; ++$idx)
{
$v1 = _add($v1, -(_add($v0 << 4 ^ _rshift($v0, 5), $v0) ^ _add($sum, $key[_rshift($sum, 11) & 3])));
$sum = _add($sum, -$delta);
$v0 = _add($v0, -(_add($v1 << 4 ^ _rshift($v1, 5), $v1) ^ _add($sum, $key[$sum & 3])));
}
$v[0] = $v0;
$v[1] = $v1;
}
function _rshift($integer, $n)
{
// convert to 32 bits
if (0xffffffff < $integer || -0xffffffff > $integer)
{
$integer = fmod($integer, 0xffffffff + 1);
}
// convert to unsigned integer
if (0x7fffffff < $integer) {
$integer -= 0xffffffff + 1.0;
}
else if (-0x80000000 > $integer)
{
$integer += 0xffffffff + 1.0;
}
// do right shift
if (0 > $integer)
{
// remove sign bit before shift
$integer &= 0x7fffffff;
// right shift
$integer >>= $n;
// set shifted sign bit
$integer |= 1 << (31 - $n);
}
else
{
// use normal right shift
$integer >>= $n;
}
return $integer;
}
function _add($i1, $i2)
{
$result = 0.0;
foreach (func_get_args() as $value)
{
// remove sign if necessary
if (0.0 > $value)
{
$value -= 1.0 + 0xffffffff;
}
$result += $value;
}
// convert to 32 bits
if (0xffffffff < $result || -0xffffffff > $result)
{
$result = fmod($result, 0xffffffff + 1);
}
// convert to signed integer
if (0x7fffffff < $result)
{
$result -= 0xffffffff + 1.0;
}
else if (-0x80000000 > $result)
{
$result += 0xffffffff + 1.0;
}
return $result;
}
Thank you for your answers.
If you need to do math with very large integers in PHP, your option is basically to use strings and the arbitrary precision library of your choice:
In this case, GMP is your only choice since the other library does not support bitwise operations.
User contributions licensed under CC BY-SA 3.0