How to read/write 64-bit unsigned little-endian integers? - php

pack doesn't support them, so how to read and write 64-bit unsigned little-endian encoded integers?

You could consider 64 bit numbers as an opaque chunk of 8 bytes of binary data and use the usual string manipulation functions to handle them (i.e. substr and the dot operator above all).
Whenever (and if) you need to perform arithmetics on them, you can use a couple of wrapper functions to encode/decode that chunk in its hexadecimal representation (in my implementation I call this intermediate type hex64) and use the external library you prefer to do the real work:
<?php
/* Save as test.php */
function chunk_to_hex64($chunk) {
assert(strlen($chunk) == 8);
return strrev(bin2hex($chunk));
}
function hex64_to_chunk($hex64) {
assert(strlen($hex64) == 16);
return strrev(pack('h*', $hex64));
}
// Test code:
function to_hex64($number) {
$hex64 = base_convert($number, 10, 16);
// Ensure the hex64 is left padded with '0'
return str_pad($hex64, 16, '0');
}
for ($number = 0.0; $number < 18446744073709551615.0; $number += 2932031007403.0) {
$hex64 = to_hex64($number);
$hex64_reencoded = chunk_to_hex64(hex64_to_chunk($hex64));
assert($hex64_reencoded == $hex64, "Result is $hex64_reencoded, expected $hex64");
}
$data = file_get_contents('test.php');
// Skip the last element because it is not 8 bytes
$chunks = array_slice(str_split($data, 8), 0, -1);
foreach ($chunks as $chunk) {
$hex64 = to_hex64($number);
$chunk_reencoded = hex64_to_chunk(chunk_to_hex64($chunk));
assert($chunk_reencoded == $chunk, "Result is $chunk_reencoded, expected $chunk");
}

I wrote a helper class for packing/unpacking 64-bit unsigned ints.
The relevant bit is just two lines:
$ints = unpack("#$offset/Vlo/Vhi", $data);
$sum = Math::add($ints['lo'], Math::mul($ints['hi'], '4294967296'));
(Math::* is a simple wrapper around bcmath)
And packing:
$out .= pack('VV', (int)bcmod($args[$idx],'4294967296'), (int)bcdiv($args[$idx],'4294967296'));
It splits the 64-bit ints into two 32-bit ints, which 64-bit PHP should support :-)

Related

How to convert a large binary string to a binary value and back

I need to take a large binary string (whose length will always be divisible by 8) ...
// 96-digit binary string
$str = '000000000011110000000000000000001111111111111111111111111111111111111111000000000000000000001111';
... then convert it to a binary value (to store in a mysql db as type varbinary), and later convert it back again to recreate that string.
This is most likely NOT a duplicate question. Every posted stackoverflow answer I could find is either broken (PHP7 apparently changed how some of these functions work) or doesn't offer a solution to this specific problem. I've tried a few things, such as ...
// get binary value from binary string
$bin = pack('H*', base_convert($str, 2, 16));
// get binary string from binary value
$str2 = str_pad(base_convert(unpack('H*', $bin)[1], 16, 2), 96, 0, STR_PAD_LEFT);
... but this doesn't actually work.
My goal is to go back and forth between the given binary string and the smallest binary value. How is this best done?
These functions convert bit strings to binary character strings and back.
function binStr2charStr(string $binStr) : string
{
$rest8 = strlen($binStr)%8;
if($rest8) {
$binStr = str_repeat('0', 8 - $rest8).$binStr;
}
$strChar = "";
foreach(str_split($binStr,8) as $strBit8){
$strChar .= chr(bindec($strBit8));
}
return $strChar;
}
function charStr2binStr(string $charStr) : string
{
$strBin = "";
foreach(str_split($charStr,1) as $char){
$strBin .= str_pad(decbin(ord($char)),8,'0', STR_PAD_LEFT);
}
return $strBin;
}
usage:
// 96-digit binary string
$str = '000000000011110000000000000000001111111111111111111111111111111111111111000000000000000000001111';
$strChars = binStr2charStr($str);
// "\x00<\x00\x00\xff\xff\xff\xff\xff\x00\x00\x0f"
//back
$strBin = charStr2binStr($strChars);

php convert decimal to hexadecimal

I am extracting a serial from a digital certificate using the built-in OpenSSL library, however, I am having trouble converting this number to hex with precision.
The extracted number is originally in decimal but I need to have it in hex.
The number I am trying to convert is: 114483222461061018757513232564608398004
Here is what I've tried:
dechex() did not work, it returns: 7fffffffffffffff
The closest I could get was this function from the php.net page but it does not convert the whole number on part of it.
function dec2hex($dec) {
$hex = ($dec == 0 ? '0' : '');
while ($dec > 0) {
$hex = dechex($dec - floor($dec / 16) * 16) . $hex;
$dec = floor($dec / 16);
}
return $hex;
}
echo dec2hex('114483222461061018757513232564608398004');
//Result: 5620aaa80d50fc000000000000000000
Here is what I am expecting:
Decimal number: 114483222461061018757513232564608398004
Expected hex: 5620AAA80D50FD70496983E2A39972B4
I can see the correction conversion here:
https://www.mathsisfun.com/binary-decimal-hexadecimal-converter.html
I need a PHP solution.
The problem is that The largest number that can be converted is ... 4294967295 - hence why it's not working for you.
This answer worked for me during a quick test, assuming you have bcmath installed on your server, and you can obtain the number as a string to start with. If you can't, i.e. it begins life as numeric variable, you'll immediately reach PHP's float limit.
// Credit: joost at bingopaleis dot com
// Input: A decimal number as a String.
// Output: The equivalent hexadecimal number as a String.
function dec2hex($number)
{
$hexvalues = array('0','1','2','3','4','5','6','7',
'8','9','A','B','C','D','E','F');
$hexval = '';
while($number != '0')
{
$hexval = $hexvalues[bcmod($number,'16')].$hexval;
$number = bcdiv($number,'16',0);
}
return $hexval;
}
Example:
$number = '114483222461061018757513232564608398004'; // Important: already a string!
var_dump(dec2hex($number)); // string(32) "5620AAA80D50FD70496983E2A39972B4"
Ensure you pass a string into that function, not a numeric variable. In the example you provided in the question, it looks like you can obtain the number as a string initially, so should work if you have bc installed.
Answered by lafor.
How to convert a huge integer to hex in php?
function bcdechex($dec)
{
$hex = '';
do {
$last = bcmod($dec, 16);
$hex = dechex($last).$hex;
$dec = bcdiv(bcsub($dec, $last), 16);
} while($dec>0);
return $hex;
}
Example:
$decimal = '114483222461061018757513232564608398004';
echo "Hex decimal : ".bcdechex($decimal);
This is a big integer, so you need to use a big-integer library like GMP:
echo gmp_strval('114483222461061018757513232564608398004', 16);
// output: 5620aaa80d50fd70496983e2a39972b4
Try this 100% working for any number
<?php
$dec = '114483222461061018757513232564608398004';
// init hex array
$hex = array();
while ($dec)
{
// get modulus // based on docs both params are string
$modulus = bcmod($dec, '16');
// convert to hex and prepend to array
array_unshift($hex, dechex($modulus));
// update decimal number
$dec = bcdiv(bcsub($dec, $modulus), 16);
}
// array elements to string
echo implode('', $hex);
?>

Is there a way to perform binary math on binary strings in PHP?

I'm working with some numbers that would be far too large for PHP to handle (IPv6 addresses in binary), so to work around this I've formulated a function to convert the addresses to/from binary strings (literally just a string of 1s and 0s). However, when it comes to subnet calculations the easiest way I know how to do this is to use binary math. I have no idea how I'd use binary math in this instance since I'm technically dealing with strings, not binary numbers. Is there any way I can do binary math with a string representation of a binary number?
When working with large binary numbers you could use the GMP extension. It accepts strings of arbitrary length as parameters.
The answer was actually simple once I thought about it. I just wrote my own function to split the array into 32-character segments (to allow for 32-bit system compatibility, since 2^32 is the highest an integer can be on a 32-bit PHP implementation), perform the operation on each segment, and piece it back together afterward. However, the function enforces a binary string length of 128 (padding shorter ones if they're passed in) which is all I need. It could be easily reworked to allow any length of binary string. Here is the function:
function ipm_binmath($a, $b, $operand){
$binregex = "/\b[01]*\b/";
if (strlen($a) > 128 || strlen($b) > 128){
throw new Exception("ipm_binmath accepts binary strings no greater than 128 characters.");
}
preg_match($binregex, $a, $amatches);
preg_match($binregex, $b, $bmatches);
if ($amatches[0] != $a){
throw new Exception("Invalid data passed to ipm_binmath - \$a is not a binary string.");
}
if ($bmatches[0] != $b){
throw new Exception("Invalid data passed to ipm_binmath - \$b is not a binary string.");
}
$aarr = str_split(str_pad($a, 128, "0", STR_PAD_LEFT), 32);
$barr = str_split(str_pad($b, 128, "0", STR_PAD_LEFT), 32);
$ret = "";
for ($i=0; $i<4; $i++){
switch (strtoupper(trim($operand))){
case "AND":
$ret .= str_pad(decbin((bindec($aarr[$i]) & bindec($barr[$i]))), 32, "0", STR_PAD_LEFT);
break;
case "OR":
$ret .= str_pad(decbin((bindec($aarr[$i]) | bindec($barr[$i]))), 32, "0", STR_PAD_LEFT);
break;
case "XOR":
$ret .= str_pad(decbin((bindec($aarr[$i]) ^ bindec($barr[$i]))), 32, "0", STR_PAD_LEFT);
break;
default:
throw new Exception("Unsupported or invalid operand passed to ipm_binmath: '" . $operand . "'");
break;
}
}
return $ret;
}

Workaround needed, PHP dechex maximum integer [duplicate]

I have some large HEX values that I want to display as regular numbers, I was using hexdec() to convert to float, and I found a function on PHP.net to convert that to decimal, but it seems to hit a ceiling, e.g.:
$h = 'D5CE3E462533364B';
$f = hexdec($h);
echo $f .' = '. Exp_to_dec($f);
Output: 1.5406319846274E+19 = 15406319846274000000
Result from calc.exe = 15406319846273791563
Is there another method to convert large hex values?
As said on the hexdec manual page:
The function can now convert values
that are to big for the platforms
integer type, it will return the value
as float instead in that case.
If you want to get some kind of big integer (not float), you'll need it stored inside a string. This might be possible using BC Math functions.
For instance, if you look in the comments of the hexdec manual page, you'll find this note
If you adapt that function a bit, to avoid a notice, you'll get:
function bchexdec($hex)
{
$dec = 0;
$len = strlen($hex);
for ($i = 1; $i <= $len; $i++) {
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
}
return $dec;
}
(This function has been copied from the note I linked to; and only a bit adapted by me)
And using it on your number:
$h = 'D5CE3E462533364B';
$f = bchexdec($h);
var_dump($f);
The output will be:
string '15406319846273791563' (length=20)
So, not the kind of big float you had ; and seems OK with what you are expecting:
Result from calc.exe =
15406319846273791563
Hope this help ;-)
And, yes, user notes on the PHP documentation are sometimes a real gold mine ;-)
hexdec() switches from int to float when the result is too large to be represented as an int. If you want arbitrarily long values, you're probably going to have to roll your own conversion function to change the hex string to a GMP integer.
function gmp_hexdec($n) {
$gmp = gmp_init(0);
$mult = gmp_init(1);
for ($i=strlen($n)-1;$i>=0;$i--,$mult=gmp_mul($mult, 16)) {
$gmp = gmp_add($gmp, gmp_mul($mult, hexdec($n[$i])));
}
return $gmp;
}
print gmp_strval(gmp_hexdec("D5CE3E462533364B"));
Output: 15406319846273791563
$num = gmp_init( '0xD5CE3E462533364B' ); // way to input a number in gmp
echo gmp_strval($num, 10); // display value in decimal
That's the module to use. Convert it to a function and then use on your numbers.
Note: provide these hex numbers as strings so:
$num = "0x348726837469972346"; // set variable
$gmpnum = gmp_init("$num"); // gmp number format
echo gmp_strval($gmpnum, 10); // convert to decimal and print out
1.5406319846274E+19 is a limited representation of you number. You can have a more complete one by using printf()
printf("%u\n", hexdec($h));
...will output "15406319846273792000". PHP uses floats for such big numbers, so you may lose a bit of precision. If you have to work with arbitrary precision numbers, you may try the bcmath extension. By splitting the hex into two 32-bit words (which should be safe on most systems) you should be able to get more precision. For instance:
$f = bcadd(bcmul(hexdec(substr($h, 0, -8)), 0x100000000), hexdec(substr($h, 8)));
...would set $f to 15406319846273791563.
Convert HEX to DEC is easy.. But, reconstruct back hexadecimal number is very hard.
Try to use base_convert ..
$hexadecimal = base_convert(2826896153644826, 10, 16);
// result: a0b0c0d0e0f1a
Run into this issue while storing 64-bit keys in MySQL database. I was able to get a bit perfect conversion to a 64-bit signed integer (PHP limitation) using a few binary operators: (This code is 16x faster than bchexdec function and resulting variables are using half the memory on average).
function x64toSignedInt($k){
$left = hexdec(substr($k,0,8));
$right = hexdec(substr($k,8,8));
return (int) ($left << 32) | $right;
}
MySQL signed BIGINT datatype is a great match for this as an index or storage in general. HEX(column) is a simple way to convert it back to HEX within the SQL query for use elsewhere.
This solution also uses the BC Math Functions. However, an algorithm is used which does without the bcpow function. This function is a bit shorter and faster than the accepted solution, tested on PHP 7.4.
function hexDecBc(string $hex) : string
{
for ($dec = '0', $i = 0; $i < strlen($hex); $i++) {
$dec = bcadd(bcmul($dec,'16'),(string)hexdec($hex[$i]));
}
return $dec;
}
Make sure to enable gmp extension. ext-gmp
$number = gmp_strval(gmp_init('0x03....')); // outputs: 1234324....
Doesn't intval(var, base) take care of it?
From the PHP Manual.

Converting long int to string in PHP

I'm using FMS along with PHP and I need the client's ID in order to disconnect some user at some point. So, I retrieve client's ID from FMS, but FMS sends the ID as a long int, such as 4702111234508538223.
Here's my problem; I need to convert this number to something like oAACAAAA in PHP. Is there any short way or some kind of library exists to doing this? Otherwise I have to convert this AS3 library into PHP.
This function converts something like "4702111234525315439" into something like "oAADAAAA":
function convert_id_to_str($id)
{
if (strspn($id, '0123456789') != strlen($id)) {
return false;
}
$str = '';
if (PHP_INT_SIZE >= 8) {
while ($id) {
$str .= chr($id & 255);
$id >>= 8;
}
} else {
while ($id) {
$str .= chr(bcmod($id, '256'));
$id = bcdiv($id, '256', 0);
}
}
return $str;
}
You could use either BC Math or GMP PHP functionalities to be sure to handle 64-bit number on 32 and 64-bit PHP capable server, and then pack the result in a formatted string, e.g. :
$id = "4702111234508538223";
$hi = bcdiv($id, pow(2, 32));
$lo = bcsub($id, bcmul($hi, pow(2, 32)));
var_dump(pack("LL", $lo, $hi));
Returns string(8) "oAACAAAA".
The code is exploded but could easily be turned to a one-liner or function as well. The use of a big number extension ensure compatibility with 32-bits platforms but if you're sure the platform hosting your PHP interpreter have 64-bit capabilities you could just use
pack("LL", $id, $id / pow(2, 32));

Categories