0

When running PHP on 64 bits machines max integer value is 0x7fffffffffffffff; differently noted 9223372036854775807 (except on Windows prior to PHP 7, where it was always 32 bit).

According to the related PHP manual page

PHP does not support unsigned integers.

This is a bit of a problem when wanting to port C sourcecode, which uses uint64_t values, to PHP. Especially, when looking at cryptographic code which heavily relies on bit-shifting, rotations, etc.

What is the correct (read: recommendable) way to handle 64 bit unsigned integers (uint64) values, while avoiding the need to fall back on libs like GMP or bcmath trying to work around the problem?

e-sushi
  • 13,786
  • 10
  • 38
  • 57

2 Answers2

2

There is no recommended way to handle unsigned integers besides using the mentioned extensions.

You can try this code:

/// portably build 64bit id from 32bit hi and lo parts
function _Make64 ( $hi, $lo )
{
// on x64, we can just use int
if ( ((int)4294967296)!=0 )
    return (((int)$hi)<<32) + ((int)$lo);
// workaround signed/unsigned braindamage on x32
$hi = sprintf ( "%u", $hi );
$lo = sprintf ( "%u", $lo );
// use GMP or bcmath if possible
if ( function_exists("gmp_mul") )
    return gmp_strval ( gmp_add ( gmp_mul ( $hi, "4294967296" ), $lo ) );
if ( function_exists("bcmul") )
    return bcadd ( bcmul ( $hi, "4294967296" ), $lo );
// compute everything manually
$a = substr ( $hi, 0, -5 );
$b = substr ( $hi, -5 );
$ac = $a*42949; // hope that float precision is enough
$bd = $b*67296;
$adbc = $a*67296+$b*42949;
$r4 = substr ( $bd, -5 ) +  + substr ( $lo, -5 );
$r3 = substr ( $bd, 0, -5 ) + substr ( $adbc, -5 ) + substr ( $lo, 0, -5 );
$r2 = substr ( $adbc, 0, -5 ) + substr ( $ac, -5 );
$r1 = substr ( $ac, 0, -5 );
while ( $r4>100000 ) { $r4-=100000; $r3++; }
while ( $r3>100000 ) { $r3-=100000; $r2++; }
while ( $r2>100000 ) { $r2-=100000; $r1++; }
$r = sprintf ( "%d%05d%05d%05d", $r1, $r2, $r3, $r4 );
$l = strlen($r);
$i = 0;
while ( $r[$i]=="0" && $i<$l-1 )
    $i++;
return substr ( $r, $i );
}
list(,$a) = unpack ( "N", "\xff\xff\xff\xff" );
list(,$b) = unpack ( "N", "\xff\xff\xff\xff" );
$q = _Make64($a,$b);
var_dump($q);

Found here: https://www.percona.com/blog/2007/03/27/integers-in-php-running-with-scissors-and-portability/

EmilCataranciuc
  • 1,024
  • 1
  • 11
  • 24
0

The recommended way to handle 64 bit unsigned integers in PHP (64 bit) is to make sure that the sign bit of the PHP (result)integer (msb) does not overflow as a result of using a PHP operator.

Without overflow the resulting (signed) PHP integer will represent/mimic the unsigned integer type (uint64_t).

Common operators in cryptography are: arithmetic (+, -, * and %) and bitwise (^, >> and <<).

To demonstrate the handling of these PHP operators with unsigned integer operands I wrote a Uint64 class. It includes casting functions (to/from string) as well.

Link: https://www.siderea.nl/php/class.uint64.txt

<?

class Uint64
{
    /*
        PHP class enables handling of the unsigned (64-bit) integer type

        version: 1.2

        Requires 64-bit build of PHP

        Functions using Arithmetic Operators:
        addition +  -> Add64($a,$b) -> returns ($a + $b)
        subtract -  -> Sub64($a,$b) -> returns ($a - $b)
        multiply *  -> Mul64($a,$b) -> returns ($a * $b)
        modulus  %  -> Mod64($a,$d) -> returns ($a % $d)
        Returned integer mimics the 64-bit unsigned integer type

        Bitwise operators in PHP will do fine except for the bitwise right shift ($x >> $bits)
        For correct usage of bitwise right shifts in PHP do -> ($x >> $bits) & (PHP_INT_MAX >> ($bits - 1))

        Casting functions.
        str2int()   raw/binary (8 byte) string to integer
        int2str()   integer to raw/binary (8 byte) string
        hex2int()   16 character hex string to integer
        int2dec()   integer to 20 character unsigned decimal string
        int2hex()   integer to 16 character hex string

        NOT yet supported:
        - division operator (/)

        New functions soon to be added:
        - Add64carry  -> returns sum and carry (carry as a third function argument by reference)
        - Mul64.128   -> returning 128 bit unsigned integer as 2 element array (64 upper bits, 64 lower bits)
    */

    public function str2int($str)
    {
        $split = unpack('N2', $str);

        return ($split[1] << 32) | $split[2];
    }

    public function int2str($int)
    {
        return hex2bin(sprintf('%016x',$int));
    }

    public function hex2int($str)
    {
        $split = unpack('N2', hex2bin($str));

        return ($split[1] << 32) | $split[2];
    }

    public function int2dec($int)
    {
        return sprintf('%020u',$int);
    }

    public function int2hex($int)
    {
        return sprintf('%016x',$int);
    }

    public function Mod64($a, $d)
    {
        if ($a < 0)
        {
            $mod = (($a & PHP_INT_MAX) % $d) + (PHP_INT_MAX % $d) + 1;

            if ($mod < $d)
            {
                return $mod;
            }

            return $mod - $d;
        }

        return $a % $d;
    }

    public function Mul64($a, $b)
    {
        $min    = ~PHP_INT_MAX;
        //$min  = PHP_INT_MIN;

        $mask60 = 0x000000000000000f;
        $mask34 = 0x000000003fffffff;

        $aL = $a & $mask60;
        $bH = ($b >> 60) & $mask60;

        $bL = $b & $mask60;
        $aH = ($a >> 60) & $mask60;

        $La = $a & $mask34;
        $Ha = ($a >> 30) & $mask34;

        $Lb = $b & $mask34;
        $Hb = ($b >> 30) & $mask34;

        $sum1 = (($La * $Hb) + ($Ha * $Lb)) << 30;
        $sum2 = ((($Ha * $Hb) + ($aL * $bH) + ($bL * $aH)) << 60) | ($La * $Lb);

        //
        $sum = $min + ($sum1 & PHP_INT_MAX) + ($sum2 & PHP_INT_MAX);

        if (($sum1 ^ $sum2) < 0)
        {}
        else
        {
            $sum ^= $min;
        }

        return $sum;
    }

    public function Add64($a, $b)
    {
        $min    = ~PHP_INT_MAX;
        //$min  = PHP_INT_MIN;

        $sum = $min + ($a & PHP_INT_MAX) + ($b & PHP_INT_MAX);

        if (($a ^ $b) < 0)
        {
            return $sum;
        }

        return $sum ^ $min;
    }

    public function Sub64($a, $b)
    {
        $min    = ~PHP_INT_MAX;
        //$min  = PHP_INT_MIN;

        if ($a < 0)
        {
            $a &= PHP_INT_MAX;

            if ($b < 0)
            {
                $b &= PHP_INT_MAX;

                if ($a < $b)
                {
                    return $min + (PHP_INT_MAX - $b) + $a + 1;
                }
                else
                {
                    return $a - $b;
                }
            }
            else
            {
                if ($a < $b)
                {
                    return ($min + (PHP_INT_MAX - $b) + $a + 1) ^ $min;
                }
                else
                {
                    return ($a - $b) ^ $min;
                }
            }
        }
        else
        {
            if ($b < 0)
            {
                $b &= PHP_INT_MAX;

                if ($a < $b)
                {
                    return ($min + (PHP_INT_MAX - $b) + $a + 1) ^ $min;
                }
                else
                {
                    return ($a - $b) ^ $min;
                }
            }
            else
            {
                if ($a < $b)
                {
                    return $min + (PHP_INT_MAX - $b) + $a + 1;
                }
                else
                {
                    return $a - $b;
                }
            }
        }
    }
}

?>
BitsofBob
  • 1
  • 2
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/21884276) – Nico Haase Jan 09 '19 at 11:26
  • Would it be appropriate to post the complete code here ? – BitsofBob Jan 09 '19 at 11:36