How to generate a 128-bit long string? - php

Basically, I'm looking for a function to perform the following
generateToken(128)
which will return a 128-bit string consisting of integers or alphabet characters.
Clarification: From the comments, I had to change the question. Apparently, I am looking for a string that is 16 characters long if it needs to be 128 bits.

Is there a reason you must restrict the string to integers? That actually makes the problem a lot harder because each digit gives you 3.3 bits (because 2^3.3 ~= 10). It's tricky to generate exactly 128 bits of token in this manner.
Much easier is to allow hexadecimal encoding (4 bits per character). You can then generate 128 genuine random bits, then encode them in hex for use in your application. Base64 encoding (6 bits per character) is also useful for this kind of thing.
openssl_random_pseudo_bytes will give you a string of random bytes that you can use bin2hex to encode, otherwise you can use mt_rand in your own token-generation routine.
EDIT: After reading the updates to the question it seems that you want to generate a token that represents 128 bits of data and the actual string length (in characters) is not so important. If I guess your intention correctly (that this is a unique ID, possibly for identification/authentication purposes) then I'd suggest you use openssl_random_pseudo_bytes to generate the right number of bits for your problem, in this case 128 (16 bytes). You can then encode those bits in any way you see fit: hex and base64 are two possibilities.
Note that hex encoding will use 32 characters to encode 128 bits of data since each character only encodes 4 bits (128 / 4 = 32). Base64 will use 22 characters (128 / 6 = 21.3). Each character takes up 8 bits of storage but only encodes 4 or 6 bits of information.
Be very careful not to confuse encoded string length with raw data length. If you choose a 16-character string using alphanumeric characters (a-z, A-Z, 0-9) then you only get 6 bits of information per character (log base 2 of 62 is nearly 6), so your 16-character string will only encode 96 bits of information. You should think of your token as an opaque byte array and only worry about turning it into / from a character string when you actually try to send it over the wire or put it in a cookie or whatever.

As of PHP 5.3:
$rand128 = bin2hex(openssl_random_pseudo_bytes(16));

What is your purpose?
If you just want a unique id, then use uniqid:
http://www.php.net/manual/en/function.uniqid.php
Its not random, its essentially a hex string based on microtime. If you do uniqid('', true), then it will return a hex string based on microtime as well as tack on a bunch of random numbers on the end of the id (so even if two calls come in on the same microsecond, it is unlikely that they'll share a unique id).
If you need a 16-character string exactly, then what purpose? Are you salting passwords? How random should the string be? All in all, you can always just do:
$toShow = array();
for($i = 0; $i<16; $i++){
$toShow[] = chr(mt_rand(ord('a'), ord('z')));
}
return $toShow
Now this creates a string of characters that are between 'a' and 'z'. You can change "ord('a')" to 0, and "ord('z')" to 255 to get a fully random binary string... or any other range you need.

Related

Cryptographically secure random ASCII-string in PHP

I know about random_bytes() in PHP 7, and I want to use it for generating a cryptographically secure (e.g. hard to guess) random string for use as a one-time token or for longer term storage in a cookie.
Unfortunately, I don't know how to convert the output of random_bytes() to a string consisting only of human readable characters, so browsers don't get confused. I know about bin2hex(), but I'd prefer to use the full ASCII-range instead of hex numbers, for the sake of more bits per length.
Any ideas?
Unfortunately Peter O. deleted his answer after receiving negative attention in a review queue, perhaps because he phrased it as a question. I believe it is legitimate answer so I will reprise it.
One easy solution is to encode your random data into the base64 alphabet using base64_encode(). This will not produce the "full ASCII-range" as you have requested but it will give you most of it. An even larger ASCII range is output by a suitable base85 encoder, but php does not have a built-in one. You can probably find plenty of open-source base85 encoders for php though. In my opinion the decrease in length of base85 over base64 is unlikely to be worth the extra code you have to maintain.
I personally just use a GUID library and concatenate a couple of GUIDs to get a long unique token string. You also have the option to remove the dashes to keep it difficult to know the source and if you want to make it even more complex you can randomly cut back the string by up to 10 char to add complexity to its unknown length.
I use this library for generating my GUIDs
https://packagist.org/packages/ramsey/uuid
use Ramsey\Uuid\Uuid;
$token = Uuid::uuid4() . '-' . Uuid::uuid4();
Sorry, I overlooked the part about you wanting to use the full scope of 26 alpha char with numeric... Not sure I have an answer for you in this respect but you should have faith in the difficulty of guessing a UUID4, especially when you add a couple together and obfuscate the length by a factor of 10 to make guessing more complex.
Actually, if you could safely generate an array of random numbers in the range of valid ascii char codes then you could convert the entire random array of codes into the respective ascii char and implode them together as a single string.
function randomAsciiString($length) {
return implode('', array_map(
function($value) {
return chr($value);
},
array_map(
function($value) {
return random_int(33, 126);
},
array_fill(0, $length - 1, null)
)
));
}
echo randomAsciiString(128); // Normal 128 char string
echo randomAsciiString(random_int(118, 128)); // obfuscated length char string for extra complexity.
of course though... you should be mindful that you're using all the standard keys on the keyboard and some of those characters are going to upset things that are sensitive ( eg quotes etc.. )
Let's consider the letters to be used. For the sake of simplicity I will assume that you intend only big and small English letters to be used. This means that you have 26 big letters and 26 small letters, 52 different possible values. If we view a byte array of n elements as a number of n digits in base 256 and we convert this number into a base 52 number, where A is 0, B is 1, C is 2, ..., a is 26, ..., z is 51, then converting these digits into the corresponding letters will yield the text you wanted.

Why does "hexdec" produce different results?

Have played with hex values in PHP, but haven't understand this behavior yet
echo hexdec(0x80); //296
echo hexdec((string)0x80); //296
echo hexdec("0x80"); //128
hexdec() assumes its’s getting a string. In the first you’re taking a number 0x80, which is 128 in decimal, and giving it to it. This means it needs to be made into a string “128” because the default way to convert is the decimal form. When you then want that to be treated as hex it will become 296.
In the last one you actually say “here’s a string of a hex number, convert it to decimal” and the conversion is done.
Numeric literals aren’t handled as hex or dec or anything. They’re just numbers and hexdec() isn’t meant to be used with them. Only strings. This is where implicit conversions between types may cause issues.
here are some differents between string in php internal and at php layer.
0x80 is a numberic literal, it is same at the internal and language layer. it is 128 hexadecimal, convert to decimal is 296.
(string)0x80 looks like it will change to "0x80", but if you echo (string)0x80 it will output 128!!! and we know 0x80 is in Decimal is 128, so (string) action just change it to decimal string expression.
"0x80" is real string expression. it will calculate every charater to hex, so it will be (0 * 16 + x * 16 (igonre) + 8 *16 + 0 * 16) = 128.
all above is the numberic in php literal final store as decimal.
(string)number is also store decimal string expression.

PHP encoding to 64 char length

To generate some pretty URLs I would take two strings
-UNIX timestamp
-string (length 16 chars)
Is there some way to use both and encode them to a string with a fixed length of 64 chars?
You can use the str_pad() function along with base64_encode for the string:
http://php.net/manual/en/function.str-pad.php
This functions returns the input string padded on the left, the right, or both sides to the specified padding length. If the optional argument pad_string is not supplied, the input is padded with spaces, otherwise it is padded with characters from pad_string up to the limit.
Example:
$string = urlencode(base64_encode($string));
$padded = str_pad($tstamp."_".$string."_", 64, "0");
Your url could then look like:
/order/timestamp_base64string_00000..../
Each _ separator gets you the part of the URL you need, you will have to urldecode() then base64_decode() the string.
I suggest that you rather use sessions (cookies) to maintain such information from a page to another, or use an intermediary storage system whose IDs could be used in URLs.

How to get a cryptographically strong integer from 0-X in PHP?

I want to generate random alphanumeric strings in PHP. They will be used in places where the strength of random numbers is important (publicly visible IDs in URLs and the like).
As I understand, in PHP the main source of cryptographically strong randomness is openssl_random_pseudo_bytes(). This however returns an array of bytes, not alphanumeric characters.
To convert them to alphanumerics I could either hash them (which would produce a longer-than-necessary string of a limited set of hex characters), or base64_encode() them (which would produce a string with +, / and = in it - not alphanumerics).
So I think that instead I could use the random bytes as a source of entropy and generated my own string consisting only of the characters 0-9a-zA-Z.
The problem then becomes - how to translate from 256 distinct values (one byte of input) to 62 distinct value (one character of output). And in a way, that all 62 characters are equally as likely. (Otherwise there will be 8 characters that appear more often than the rest).
Or perhaps I should use another approach entirely? I would like my string to be as short as possible (say, 20 characters or so - shorter URLs are better) and consist only of alphanumeric characters (so that it doesn't need to be specially escaped anywhere).
You can implement your own base64 encoding, sort of. If you can allow two specific symbols - these can be anything, for example . and -, it doesn't really matter. It can even be a space for one of them. In any case, what you would do is this:
$alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-";
// using . and - for the two symbols here
$input = [123,193,21,13]; // whatever your input it, I'm assuming an array of bytes
$output = "";
foreach($input as $byte) {
$output .= $alphabet[$byte%64];
}
Assuming random input, all characters have equal probability of appearing.
That being said, if you can't allow anything except pure alphanumeric, cut the symbols from the $alphabet and use %62 instead of %64. While this does mean you have a small bias towards the chracters 0 through 7, I don't think it's significant enough to worry about.
I found this function on php.net in the user comments.
function crypto_rand($min,$max) {
$range = $max - $min;
if ($range == 0) return $min; // not so random...
$length = (int) (log($range,2) / 8) + 1;
return $min + (hexdec(bin2hex(openssl_random_pseudo_bytes($length,$s))) % $range);
}
Then do something like
for($i=0; $i<20; $i++)
{
$string.= chr(crypto_rand(1,26)+96); //or +64 for upper case
}
Or similar.
note: THIS IS WRONG! I leave this attempted answer for reference only.
(31 * 256) % 62 = 0
For each output alphanumeric character, generate 31 random values. Sum these 31 values and take the modulo 62.
Kind of brutal, but this is the only "mathematicaly correct" option I can think of :)

How do I get the value encoded in a SHORT (signed 16 bit number) that is explicitly MSBF in PHP

I need to unpack binary data that is encoded rather exotically: a 32 bit 2's complement bit pattern, representing a SHORT.USHORT decimal fraction, with a signed SHORT integer component and an unsigned SHORT "this many 1/65536 parts" decimal fraction component. To make things even more fun, the sign of the SHORT is determined by the first bit in the 2's complement 32 bit pattern. Not by its sign after decoding to 'real' bit pattern.
An example of this would be the following:
2's complement bit pattern: 11111111110101101010101010101100
converted 'normal' pattern: 00000000001010010101010101010100
SHORT bits (upper 16): 0000000000101001 (decimal: 41)
USHORT bits (lower 16: 0101010101010100 (decimal: 21844)
actual number encoded: -41.333 (41, negative from high MSB + 21844/65536)
(if you think this scheme is insane: it certainly seems that way, doesn't it? It's the byte format used in Type2 fonts that are encoded in a CFF block, or "compact font format" block. Crazy as it is, this format is set in stone, and we're about 20 years too late to have it changed. This is the byte layout in a CFF font, and the only thing we get to worry about now is how to correctly decode it)
Problems occur when we're dealing with patterns like these:
2's complement bit pattern: 00000000000000000000000000000001
converted pattern: 11111111111111111111111111111111
upper 16 bits: 1111111111111111 (decimal 65535 *OR* -1)
lower 16 bits: 1111111111111111 (decimal 65535)
SHORT.USHORT number: -65536 *OR* 1
Depending on who you ask, the pattern 1111111111111111 can be decoded either as 65535, such as when interpreted as a bit pattern in a larger (32 or 64 bit) number, or as -1, when interpreted as a 16 bit signed integer. The only correct interpretation here, however, is as the latter, so this leads us to the question's subject line:
what PHP code do I use to turn this 16 bit pattern into the correct number, given that PHP has no pack/unpack parameter for unpacking as 16 bit int with the most significant bit first? There is a parameter for unpacking a 16 bit int using machine-indicated byte order, but this is going to give problems because font data storage is non-negotiable: all fonts, allwhere, everywhen, must be encoded using Motorola/Big Endian byte ordering, irrespective of the machine's preferred byte ordering.
My code to going from 32-bit 2's complement to final value at the moment is this:
// read in 32 bit pattern, represenging a 2's complement pattern
$p2c = 0x01000000 * $b[x] + 0x010000 * $b[x+1] + 0x0100 * $b[x+2] + $b[x+3];
// convert 2's complement to plain form
$p = (~$p2c + 1) & 0xFFFFFFFF;
// get lower 16 bits, representing an unsigned short.
// due to unsigned-ness, this values is always correct.
$ushort = 0xFFFF & $p;
// get higher 16 bits, representing a signed short.
// due to its sign, this value can be spectacularly wrong!
$short = ($p >> 16);
// "reconstitute" the FIXED format number
$num = - ($short + round($ushort/65536,3));
This had a pretty simple answer that I completely ignored for no good reason, and of course didn't think of until I wrote this question.
$short = $pattern >> 16;
if($short >= 32768) { $short -= 65536; }
and voila.

Categories