Perl pack() function and "B" format character to Php - php

What does the "B" do in this pack statement from Perl code?
$hce_hash=pack('B*', $hce_hash);
Is there an equivalent function in PHP?

PHP’s pack doesn’t support a format of B*, but it does support H*. In Perl, you could emulate it with
sub pack_Bstar {
my($bits) = #_;
my $Hstar;
my $nybble = 0;
for (my $i = 0; $i < length $bits; ++$i) {
$nybble *= 2;
$nybble += int substr($bits, $i, 1);
if ($i % 4 == 3) {
$Hstar .= sprintf "%x", $nybble;
$nybble = 0;
}
}
my $pad = 4 - length($bits) % 4;
if ($pad != 4) {
$nybble = ($nybble << $pad);
$Hstar .= sprintf "%x", $nybble;
}
pack "H*", $Hstar;
}
The code above is not idiomatic Perl, but translation to PHP should be straightforward.
The H* format wants a hex string with high nybble (4 bits) first. The code above chews off four bits at a time to compute each nybble value. For example, for a bit string of 1011, tracing the algorithm gives
nybble = 0
nybble = 2 * 0 + 1 = 1
nybble = 2 * 1 + 0 = 2
nybble = 2 * 2 + 1 = 5
nybble = 2 * 5 + 1 = 11
10112 is indeed 1110, which is b16. If the last nybble is incomplete (between one and three bits), we left-shift the bit the appropriate number of places. This has the effect of zero-padding on the right.
Tests:
my #tests = (
["01001010011101010111001101110100" => "Just"],
["0110000101101110011011110111010001101000011001010111001" => "another"],
["01010000010010000101000000101111010100000110010101110010011011" => "PHP/Perl"],
["01101000011000010110001101101011011001010111001000101100" => "hacker,"],
);
for (#tests) {
my($input,$expect) = #$_;
my $got = pack_Bstar $input;
print "$input: ", ($got eq $expect ? "PASS" : "FAIL"), " ($got)\n";
}
Output:
01001010011101010111001101110100: PASS (Just)
0110000101101110011011110111010001101000011001010111001: PASS (another)
01010000010010000101000000101111010100000110010101110010011011: PASS (PHP/Perl)
01101000011000010110001101101011011001010111001000101100: PASS (hacker,)

pack 'B*', $s returns the bytes represented by the string of 0 and 1 characters that form up the string in $s. The value of $s is right-padded with zeros to a length divisible by 8 if necessary.
For example,
pack 'B*', '0100101000110101'
results in
chr(0b01001010) . chr(0b00110101);

As others have noted, PHP's pack() does not support the B template, which in Perl's pack() turns a bitstring, represented as a literal string of 0 and 1 characters, into a packed byte string with 8 bits per byte.
However, since PHP's pack() does support the H template, which does the same except for hexadecimal digits instead of bits, we can emulate Perl's B template in PHP by first using base_convert() to turn the bits into hex digits and then packing those:
function pack_B( $bits, $len = false ) {
// truncate input to desired length, if given:
if ( $len === false ) $len = strlen( $bits );
else $bits = substr( $bits, 0, $len );
// pad input with zeros to next multiple of 4 above $len:
$hexlen = (int)( ($len + 3) / 4 );
$bits = str_pad( $bits, 4*$hexlen, "0" );
// split input into chunks of 4 bits, convert each to hex and pack them:
$nibbles = str_split( $bits, 4 );
foreach ( $nibbles as $i => $nibble ) {
$nibbles[$i] = base_convert( $nibble, 2, 16 );
}
return pack( "H*", implode( "", $nibbles ) );
}
(The reason we can't just feed the whole input string to base_convert() is that it stores its intermediate result as a PHP float, and thus doesn't produce correct results for numbers too large to be accurately represented by a float. Doing it one hex digit at a time works fine, however.)

Related

PHP How to take first 2 bits from byte and make new byte

Operations on bits.
How to take 2 bits from byte like this:
take first 2 from 12345678 = 12;
Make new byte = 00000012
For example as asked in discussion by jspit :
$char = 'z'; //is 122, 0111 1010
$b = $char & '?'; // ? is 63, 0011 1111
echo $b; //$b becomes 58 and shows ':'
//if integer used you get:
$b = $char & 63;// 63 is 0011 1111 as '?' but $char is string and you get 0 result:
echo $b; //$b becomes 0 because conversion to integer is used from string and $char becomes 0 and get 0 & 63 = 0, and here is error.
For clearance operation is on bits not on bytes, but bits from bytes.
'string' >> 1 not work, but this is second problem.
Codes of char You can check on my site generating safe readable tokens, with byte template option on. Site is in all available languages.
I think I found good answer here:
how to bitwise shift a string in php?
PS. Sorry I cant vote yours fine answers but I have no points reputation here to do this ;)...
I hope you understand bits can only be 0 or 1, I'm assuming when you say "12345678" you're just using those decimal symbols to represent the positions of each bit. If that is the case, then you're looking for bitwise operators.
More specifically:
$new = $old >> 6;
This bitwise shift operation will shift all bits 6 positions to the right, discarding the 6 bits that were there before.
You can also use an or operation with a bitmask to ensure only 2 bits remain, in case the variable had more than 8 bits set:
$new = ($old >> 6) | 0b00000011;
function highestBitsOfByte(int $byte, int $count = 2):int {
if($count < 0 OR $count > 8) return false; //Error
return ($byte & 0xFF) >> (8-$count);
}
$input = 0b10011110;
$r = highestBitsOfByte($input,2);
echo sprintf('%08b',$r);
The integer number is limited to the lowest 8 bits with & 0xFF. Then the bits are shifted to the right according to the desired length.
example to try: https://3v4l.org/1lAvO
If there is a character as input and the fixed number of 2 bits is required, then this can be used:
$chr = 'z'; //0111 1010
$hBits = ord($chr) >> 6;
echo sprintf('%08b',$hBits); //00000001

Convert string to consistent but random 1 of 10 options

I have many strings. Each string something like:
"i_love_pizza_123"
"whatever_this_is_now_later"
"programming_is_awesome"
"stack_overflow_ftw"
...etc
I need to be able to convert each string to a random number, 1-10. Each time that string gets converted, it should consistently be the same number. A sampling of strings, even with similar text should result in a fairly even spread of values 1-10.
My first thought was to do something like md5($string), then break down a-f,0-9 into ten roughly-equal groups, determine where the first character of the hash falls, and put it in that group. But doing so seems to have issues when converting 16 down to 10 by multiplying by 0.625, but that causes the spread to be uneven.
Thoughts on a good method to consistently convert a string to a random/repeatable number, 1-10? There has to be an easier way.
Here's a quick demo how you can do it.
function getOneToTenHash($str) {
$hash = hash('sha256', $str, true);
$unpacked = unpack("L", $hash); // convert first 4 bytes of hash to 32-bit unsigned int
$val = $unpacked[1];
return ($val % 10) + 1; // get 1 - 10 value
}
for ($i = 0; $i < 100; $i++) {
echo getOneToTenHash('str' . $i) . "\n";
}
How it works:
Basically you get the output of a hash function and downscale it to desired range (1..10 in this case).
In the example above, I used sha256 hash function which returns 32 bytes of arbitrary binary data. Then I extract just first 4 bytes as integer value (unpack()).
At this point I have a 4 bytes integer value (0..4294967295 range). In order to downscale it to 1..10 range I just take the remainder of division by 10 (0..9) and add 1.
It's not the only way to downscale the range but an easy one.
So, the above example consists of 3 steps:
get the hash value
convert the hash value to integer
downscale integer range
A much shorter example with crc32() function which returns integer value right away thus allowing us to omit step 2:
function getOneToTenHash($str) {
$int = crc32($str); // 0..4294967295
return ($int % 10) + 1; // 1..10
}
below maybe what u want
$inStr = "hello world";
$md5Str = md5($inStr);
$len = strlen($md5Str);
$out = 0;
for($i=0; $i<$len; $i++) {
$out = 7*$out + intval($md5Str[$i]); // if you want more random, can and random() here
}
$out = ($out % 10 + 9)%10; // scope= [1,10]

Looking for best solution for python's base64.b32encode in php

I am having a code written in python
from base64 import b32encode, b32decode
somename = 'Karthik Bhat K'
b32encoded = b32encode(somename)
b32decoded = b32decode(b32encoded)
I want to write the b32encode and b32decode code in php because the key generated by python is used by app written in php.
Php does have base64_encode but for base32_encode I didn't find any builtins
I am currently using custom written function
// Note this functions can be improved!
function base32_encode($d): string{
// position mapping of characters
list($t, $b, $r) = array("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", "", "");
foreach(str_split($d) as $c) {
// Get 8 bits for each character
$b = $b . sprintf("%08b", ord($c));
}
// Since there are only 32 element in b32encode i.e A-Z and 2-7
// split the binary with 5 bits in each chunk
// since 2 ^ 5 is 32, gives items between 0 and 31
foreach(str_split($b, 5) as $c) {
// If any group has less than 5 bits fill it with 0 from the right
if (strlen($c) < 5)
$c = str_pad($c, 5, "0", STR_PAD_RIGHT);
// bindec converts binary to decimal
// The decimal is index in the array $t
// Get the value from the index of array $t
$r = $r . $t[bindec($c)];
}
return($r);
}
function base32_decode($d): string{
list($t, $b, $r) = array("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", "", "");
// Encode just split bit string into set of 5 character each
// This is the reverse of it. Find index in $t and convert it into
// 5 bit string and join each of them
foreach(str_split($d) as $c)
$b = $b . sprintf("%05b", strpos($t, $c));
// Each 8 bit of the string $b will give you the decoded string
foreach(str_split($b, 8) as $c) {
if (bindec($c)) { // Ignores the padding given in base32_encode
$r = $r . chr(bindec($c));
}
}
return($r);
}

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

PHP function to create 8 chars long hash ([a-z] = no numbers allowed)

I need PHP function that will create 8 chars long [a-z] hash from any input string.
So e.g. when I'll submit "Stack Overflow" it will return e.g. "gdqreaxc" (8 chars [a-z] no numbers allowed)
Perhaps something like:
$hash = substr(strtolower(preg_replace('/[0-9_\/]+/','',base64_encode(sha1($input)))),0,8);
This produces a SHA1 hash, base-64 encodes it (giving us the full alphabet), removes non-alpha chars, lowercases it, and truncates it.
For $input = 'yar!';:
mwinzewn
For $input = 'yar!!';:
yzzhzwjj
So the spread seems pretty good.
This function will generate a hash containing evenly distributed characters [a-z]:
function my_hash($string, $length = 8) {
// Convert to a string which may contain only characters [0-9a-p]
$hash = base_convert(md5($string), 16, 26);
// Get part of the string
$hash = substr($hash, -$length);
// In rare cases it will be too short, add zeroes
$hash = str_pad($hash, $length, '0', STR_PAD_LEFT);
// Convert character set from [0-9a-p] to [a-z]
$hash = strtr($hash, '0123456789', 'qrstuvwxyz');
return $hash;
}
By the way, if this is important for you, for 100,000 different strings you'll have ~2% chance of hash collision (for a 8 chars long hash), and for a million of strings this chance rises up to ~90%, if my math is correct.
function md5toabc($myMD5)
{
$newString = "";
for ($i = 0; $i < 16; $i+=2)
{
//add the first val of 0-15 to the second val of 0-15 for a range of 0-30
$myintval = hexdec(substr($myMD5, $i, $i +1) ) +
hexdec(substr($myMD5, $i+1, $i +2) );
// mod by 26 and add 97 to get to the lowercase ascii range
$newString .= chr(($myintval%26) + 97);
}
return $newString;
}
Note this introduces bias to various characters, but do with it what you will.
(Like when you roll two dice, the most common value is a 7 combined...) plus the modulo, etc...
one can give you a good a-p{8} (but not a-z) by using and modifying (the output of) a well known algo:
function mini_hash( $string )
{
$h = hash( 'crc32' , $string );
for($i=0;$i<8;$i++) {
$h{$i} = chr(96+hexdec($h{$i}));
}
return $h;
}
interesting set of constraints you posted there
how about
substr (preg_replace(md5($mystring), "/[1-9]/", ""), 0, 8 );
you could add a bit more entorpy by doing a
preg_replace($myString, "1", "g");
preg_replace($myString, "2", "h");
preg_replace($myString, "3", "i");
etc instead of stripping the digits.

Categories