Need help with "pack" for perl and php - php

I've the task to convert a crypt function someone made in perl into php code. Everything works okay except this:
Perl:
$wert = Encode::encode( "utf8", $wert );
$len=length $wert;
$pad = ($len % 16)?"0".chr(16 - ($len % 16)):"10";
$fuell = pack( "H*", $pad x (16 - $len % 16));
PHP:
$wert = utf8_encode($wert);
$len = mb_strlen($wert);
$pad = ( $len%16 ) ? '0'.chr(16 - ($len%16)) : '10';
$fuell = pack("H*", str_repeat($pad, (16 - $len % 16)));
The php version works okay for some strings. But when I have something like '2010-01-01T00:00:00.000' the perl version works without any error and the php version prints "PHP Warning: pack(): Type H: illegal hex digit".
I'm very grateful if someone can spot the error in the php version.
Edit:
This is the complete function I've to convert into php. It was made by a programmer of a company which doesn't work for us anymore so I can't really tell what the original intention was.
sub crypt
{
my $self = shift;
my ($wert,$pw)= #_;
$wert = Encode::encode( "utf8", $wert );
$pw = Encode::encode( "utf8", $pw );
$len=length $wert;
$pad = ($len % 16)?"0".chr(16 - ($len % 16)):"10";
$fuell = pack( "H*", $pad x (16 - $len % 16));
$wert=$wert.$fuell;
$lenpw=length $pw;
$fuell = ($lenpw % 16)? pack ("H*", "00" x (16 - $lenpw % 16)):"";
$pw=$pw.$fuell;
$cipher = new Crypt::Rijndael $pw, Crypt::Rijndael::MODE_CBC;
$cipher->set_iv($pw);
$crypted = encode_base64($cipher->encrypt($wert),"");
return $crypted;
}

It looks like the error is actually in both versions. The format code H looks for a hex digit, and as noted in the PHP error, it isn't finding (a legal) one. The culprit appears to be this expression:
chr(16 - ($len % 16))
The Perl version isn't complaining because Perl's version of pack will convert the character regardless of whether or not it is a hex digit (which may not be what you want). The documentation goes into more detail about what actually happens.
To prevent the error, try this instead:
sprintf('%x', 16 - ($len % 16))
Note: While this should fix the error you are getting, I don't know if it's an acceptable solution because I don't know the exact intent of the original author of the Perl code.

It seems that the Perl implementation of pack() is tolerant of invalid hex digits in the input string, and the PHP version is decidedly not.
Consider:
print pack("H*", "ZZ");
This prints 3 in Perl (for some reason), but results in the error you mentioned in PHP.
I'm not sure exactly what Perl is doing with these 'digits', but it's definitely not the same as PHP.
EDIT: It looks like, Perl actually will "roll" the hex digit domain forward into the character set. That is:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ #-- Give this to Perl...
0123456789ABCDEF0123456789ABCDEF0123 #-- .. and it's treated as this hex digit
Thus, "ZZ" is the same as "33", which is why it prints 3. Note that this behavior is not well-defined according to the documentation. Thus the original implementation in Perl can be considered buggy, since it relies on behavior that isn't well-defined.

Related

how to convert binary into base64? [duplicate]

I know this is a pretty silly question, but I don't know what to do.
I have an arbitrary binary number, say,
1001000000110010000000100100000010000011000000010001000001011000110000110000011100011100000011000000010010011000100000000000000100100000010110001100001000000111
I want to convert it to Base 64 using PHP - and every way I try gives me a different result. Even different online converters convert it differently:
http://home2.paulschou.net/tools/xlate/
http://convertxy.com/index.php/numberbases/
PHP's base_convert only works up to base36, and base64_encode expects a string.
What do I do?
UPDATE: I implemented the solution functions suggested by #binaryLV, and it did work well.
However, I compared the results to PHP's built-in base_convert. It turned out that base_convert to base36 returns shorter values that the custom base64 function! (And yes, I did prepend a '1' to all the binary numbers to ensure leading zeros aren't lost).
I have noticed, too, that base_convert is quite innacurate with large numbers. So I need is a function which works like base_convert, but accurately and, preferably, up to base 64.
Length of a string in example is 160. It makes me think that it holds info about 160/8 characters. So,
split string into parts, each part holds 8 binary digits and describes single character
convert each part into a decimal integer
build a string from characters, that are made from ASCII codes from 2nd step
This will work with strings with size n*8. For other strings (e.g., 12 binary digits) it will give unexpected results.
Code:
function bin2base64($bin) {
$arr = str_split($bin, 8);
$str = '';
foreach ( $arr as $binNumber ) {
$str .= chr(bindec($binNumber));
}
return base64_encode($str);
}
$bin = '1001000000110010000000100100000010000011000000010001000001011000110000110000011100011100000011000000010010011000100000000000000100100000010110001100001000000111';
echo bin2base64($bin);
Result:
kDICQIMBEFjDBxwMBJiAASBYwgc=
Here's also function for decoding it back to string of binary digits:
function base64bin($str) {
$result = '';
$str = base64_decode($str);
$len = strlen($str);
for ( $n = 0; $n < $len; $n++ ) {
$result .= str_pad(decbin(ord($str[$n])), 8, '0', STR_PAD_LEFT);
}
return $result;
}
var_dump(base64bin(bin2base64($bin)) === $bin);
Result:
boolean true
PHP has a built in base 64 encoding function, see documentation here. If you want the decimal value of the binary string first use bin2dec, there are similar functions for hexadecimals by the way. The documentation is your friend here.
[EDIT]
I might have misunderstood your question, if you want to convert between actual bases (base 2 and 64) use base_convert
$number = 1001000000110010000000100100000010000011000000010001000001011000110000110000011100011100000011000000010010011000100000000000000100100000010110001100001000000111;
echo base64_encode ($number);
This is if you want the exact string be converted into Base 64.
To convert a binary number (2 base) to a 64 base use the base_convert function.
$number = 1001000000110010000000100100000010000011000000010001000001011000110000110000011100011100000011000000010010011000100000000000000100100000010110001100001000000111;
base_convert ($number , 2, 64);

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.

PHP: bin2hex and working with impossible numbers

Haven't slept in awhile, so I'm probably missing something simple. Basically, I am taking a number and converting it to three characters. Max number of possibilities is 256*256*256 (16777216). I convert it with:
public function s_encode($num) {
$num = chr($num / 65536).chr($num / 256).chr($num % 256);
return bin2hex($num);
}
And convert it back with:
public function s_decode($hex) {
$a = pack("H*", $hex);
$b = ord(substr($a, 1, 1));
$c = ord(substr($a, 2, 1));
$d = ord(substr($a, 0, 1));
return (($d * 65536) + ($b * 256)) + $c;
}
What's strange, is this actually works. It does what I want it to, but how could it? In the first code, where I convert it to three characters, the second part of the conversion is:
chr($num / 256)
If the number is greater than 65536, this should cause an error, but it doesn't. If I were to use unpack instead of bin2hex, it will cause an error. bin2hex won't. Why and how is bin2hex so magical?
chr() only looks at the lowest 8 bits of its input:
echo "'".chr(320)."'";
yields...
'#'
as does...
echo "'".chr(64)."'";
http://ideone.com/65Itz
According to the comments in the php docs, chr will take the parameter modulo 256. Even negative integers work. bin2hex doesn't do that operation, and fails on invalid inputs.

How does this code extract the signature?

I have to debug an old PHP script from a developer who has left the company. I understand the most part of the code, except the following function. My question: What does...
if($seq == 0x03 || $seq == 0x30)
...mean in context of extracting the signature out of an X.509 certificate?
public function extractSignature($certPemString) {
$bin = $this->ConvertPemToBinary($certPemString);
if(empty($certPemString) || empty($bin))
{
return false;
}
$bin = substr($bin,4);
while(strlen($bin) > 1)
{
$seq = ord($bin[0]);
if($seq == 0x03 || $seq == 0x30)
{
$len = ord($bin[1]);
$bytes = 0;
if ($len & 0x80)
{
$bytes = ($len & 0x0f);
$len = 0;
for ($i = 0; $i < $bytes; $i++)
{
$len = ($len << 8) | ord($bin[$i + 2]);
}
}
if($seq == 0x03)
{
return substr($bin,3 + $bytes, $len);
}
else
{
$bin = substr($bin,2 + $bytes + $len);
}
}
else
{
return false;
}
}
return false;
}
An X.509 certificate contains data in multiple sections (called Tag-Length-Value triplets). Each section starts with a Tag byte, which indicates the data format of the section. You can see a list of these data types here.
0x03 is the Tag byte for the BIT STRING data type, and 0x30 is the Tag byte for the SEQUENCE data type.
So this code is designed to handle the BIT STRING and SEQUENCE data types. If you look at this part:
if($seq == 0x03)
{
return substr($bin,3 + $bytes, $len);
}
else // $seq == 0x30
{
$bin = substr($bin,2 + $bytes + $len);
}
you can see that the function is designed to skip over Sequences (0x30), until it finds a Bit String (0x03), at which point it returns the value of the Bit String.
You might be wondering why the magic number is 3 for Bit String and 2 for Sequence. That is because in a Bit String, the first value byte is a special extra field which indicates how many bits are unused in the last byte of the data. (For example, if you're sending 13 bits of data, it will take up 2 bytes = 16 bits, and the "unused bits" field will be 3.)
Next issue: the Length field. When the length of the Value is less than 128 bytes, the length is simply specified using a single byte (the most significant bit will be 0). If the length is 128 or greater, then the first length byte has bit 7 set, and the remaining 7 bits indicates how many following bytes contain the length (in big-endian order). More description here. The parsing of the length field happens in this section of the code:
$len = ord($bin[1]);
$bytes = 0;
if ($len & 0x80)
{
// length is greater than 127!
$bytes = ($len & 0x0f);
$len = 0;
for ($i = 0; $i < $bytes; $i++)
{
$len = ($len << 8) | ord($bin[$i + 2]);
}
}
After that, $bytes contains the number of extra bytes used by the length field, and $len contains the length of the Value field (in bytes).
Did you spot the error in the code? Remember,
If the length is 128 or greater, then the first length byte has bit 7
set, and the remaining 7 bits indicates how many following bytes
contain the length.
but the code says $bytes = ($len & 0x0f), which only takes the lower 4 bits of the byte! It should be:
$bytes = ($len & 0x7f);
Of course, this error is only a problem for extremely long messages: it will work fine as long as the length value will fit within 0x0f = 15 bytes, meaning the data has to be less than 256^15 bytes. That's about a trillion yottabytes, which ought to be enough for anybody.
As Pateman says above, you just have a logical if, we're just checking if $seq is either 0x30 or 0x03.
I have a feeling you already know that though, so here goes. $seq is the first byte of the certificate, which is probably either the version of the certificate or the magic number to denote that the file is a certificate (also known as "I'm guessing this because 10:45 is no time to start reading RFCs").
In this case, we're comparing against 0x30 and 0x03. These numbers are expressed in hexadecimal (as is every number starting with 0x), which is base-16. This is just really a very convenient shorthand for binary, as each hex digit corresponds to exactly four binary bits. A quick table is this:
0 = 0000
1 = 0001
2 = 0010
3 = 0011
...
...
E = 1110
F = 1111
Equally well, we could have said if($seq == 3 || $seq == 48), but hex is just much easier to read and understand in this case.
I'd hazard a guess that it's a byte-order-independent check for version identifier '3' in an x.509 certificate. See RFC 1422, p7. The rest is pulling the signature byte-by-byte.
ord() gets the value of the ASCII character you pass it. In this case it's checking to see if the ASCII character is either a 0 or end of text (according to this ASCII table).
0x03 and 0x30 are hex values. Look that up and you'll have what $seq is matching to

How to implement a Longitudinal Redundancy Check (LRC/CRC8/XOR8) checksum in PHP?

I'm having real problems trying to implement a XOR8/LRC checksum in PHP, according to the algorithm present here: http://en.wikipedia.org/wiki/Longitudinal_redundancy_check
What I'm trying to do is, given any string calculate its LRC checksum.
For example, I know for sure this string:
D$1I 11/14/2006 18:15:00 1634146 3772376 3772344 3772312 3772294 1*
Has a hexadecimal checksum of 39 (including the last * char).
For anyone interested what is the meaning of the string, it's is a DART (Deep-ocean Assesment and Reporting of Tsunamis) message - http://nctr.pmel.noaa.gov/Dart/Pdf/dartMsgManual3.01.pdf.
I convert the string to a binary string with 1's and 0's. From there, I try to create a byte array and apply the algorithm to the byte array, but it's not working and I can't figure out why.
The function I'm using for converting to String to Binary String is:
function str2binStr($str) {
$ret = '';
for ($i = 0, $n = strlen($str); $i < $n; ++$i)
$ret .= str_pad(decbin(ord($str[$i])), 8, 0, STR_PAD_LEFT);
return $ret;
}
The function I'm using for converting from Binary String to Binary Array is:
function byteStr2byteArray($s) {
return array_slice(unpack("C*", "\0".$s), 1);
}
Finally, the LRC implementation I'm using, with bitwise operators, is:
function lrc($byteArr) {
$lrc = 0;
$byteArrLen = count($byteArr);
for ($i = 0; $i < $byteArrLen; $i++) {
$lrc = ($lrc + $byteArr[$i]) & 0xFF;
}
$lrc = (($lrc ^ 0xFF) + 1) & 0xFF;
return $lrc;
}
Then, we convert the final decimal result of the LRC checksum with dechex($checksum + 0), so we have the final hexadecimal checksum.
After all these operations, I'm not getting the expected result, so any help will be highly appreciated.
Thanks in advance.
Also, I can't make it work following the CRC8-Check in PHP answer.
I'm afraid that nobody on StackOverflow can help you, and here's why. This question was bugging me so I went to the DART website you mentionned to take a look at their specs. Two problems became apparent:
The first one is you have misunderstood part of their specs. Messages start with a Carriage Return (\r or \0x0D) and the asterisk * is not part of the checksum
The second, bigger problem is that their specs contain several errors. Some of them may originate from bad copy/paste and/or an incorrect transformation from Microsoft .doc to PDF.
I have taken the time to inspect some of them so that would be nice if you could contact the specs authors or maintainers so they can fix them or clarify them. Here is what I've found.
2.1.2 The message breakdown mentions C/I as message status even though it doesn't appear in the example message.
2.1.3 The checksum is wrong, off by 0x31 which corresponds to the character 1.
2.2.3 The six checksums are wrong, off by 0x2D which corresponds to the character -.
2.3.1.2 I think there's a <cr> missing between dev3 and tries
2.3.1.3 The checksum is off by 0x0D and there's no delimiter between dev3 and tries. The checksum would be correct if there was a carriage return between the dev3 value and the tries value.
2.3.2.2-3 Same as 2.3.1.2-3.
2.3.3.3 Wrong checksum again, and there's no delimiter before tries.
2.4.2 The message breakdown mentions D$2 = message ID which should be D$3 = message ID.
Here's the code I used to verify their checksums:
$messages = array(
"\rD\$0 11/15/2006 13:05:28 3214.2972 N 12041.3991 W* 46",
"\rD\$1I 11/14/2006 18:15:00 1634146 3772376 3772344 3772313 3772294 1* 39",
"\rD\$1I 11/14/2006 19:15:00 1634146 3772275 3772262 3772251 3772249 1* 38",
"\rD\$1I 11/14/2006 20:15:00 1634146 3772249 3772257 3772271 3772293 1* 3E",
"\rD\$1I 11/14/2006 21:15:00 1634146 3772315 3772341 3772373 3772407 1* 39",
"\rD\$1I 11/14/2006 22:15:00 1634146 3772440 3772472 3772506 3772540 1* 3C",
"\rD\$1I 11/14/2006 23:15:00 1634146 3772572 3772603 3772631 3772657 1* 3B",
"\rD\$2I 00 tt 18:32:45 ts 18:32:00 3772311\r00000063006201* 22",
"\rD\$2I 01 tt 18:32:45 ts 18:32:00 3772311\r000000630062706900600061005f005ffffafff9fff8fff8fff7fff6fff401* 21",
"\rD\$2I 02 tt 18:32:45 ts 18:32:00 3772335\rfffdfffafff7fff5fff1ffeeffea00190048ffe1ffddffdaffd8ffd5ffd101* 21"
);
foreach ($messages as $k => $message)
{
$pos = strpos($message, '*');
$payload = substr($message, 0, $pos);
$crc = trim(substr($message, $pos + 1));
$checksum = 0;
foreach (str_split($payload, 1) as $c)
{
$checksum ^= ord($c);
}
$crc = hexdec($crc);
printf(
"Expected: %02X - Computed: %02X - Difference: %02X - Possibly missing: %s\n",
$crc, $checksum, $crc ^ $checksum, addcslashes(chr($crc ^ $checksum), "\r")
);
}
For what it's worth, here's a completely unoptimized, straight-up implementation of the algorithm from Wikipedia:
$buffer = 'D$1I 11/14/2006 18:15:00 1634146 3772376 3772344 3772312 3772294 1*';
$LRC = 0;
foreach (str_split($buffer, 1) as $b)
{
$LRC = ($LRC + ord($b)) & 0xFF;
}
$LRC = (($LRC ^ 0xFF) + 1) & 0xFF;
echo dechex($LRC);
It results in 0x0E for the string from your example, so either I've managed to fudge the implementation or the algorithm that produced 0x39 is not the same.
I realize that this question pretty old, but I had trouble figuring out how to do this. It's working now, so I figured I should paste the code. In my case, the checksum needs to return as an ASCII string.
public function getLrc($string)
{
$LRC = 0;
// Get hex checksum.
foreach (str_split($string, 1) as $char) {
$LRC ^= ord($char);
}
$hex = dechex($LRC);
// convert hex to string
$str = '';
for($i=0;$i<strlen($hex);$i+=2) $str .= chr(hexdec(substr($hex,$i,2)));
return $str;
}

Categories