Home » Php » php – Help me understand pack(), openssl_random_pseudo_bytes() and mt_rand() for salting passwords

php – Help me understand pack(), openssl_random_pseudo_bytes() and mt_rand() for salting passwords

Posted by: admin July 12, 2020 Leave a comment

Questions:

I’m building an application that will have a user base, and I’m at the point of securing the login. I’m fairly new to programming (and PHP,) but my efforts thus far have pointed to using Crypt() and a Blowfish hashed salt.

Before I go further, let me specify that I am not interested in phpass at this time.

Within the crypt() documentation, a user recently posted this:

<?php 
   $salt = substr(str_replace('+', '.', base64_encode(pack('N4', mt_rand(), mt_rand(), mt_rand(), mt_rand()))), 0, 22); 
?>

It is intended for use on systems
where mt_getrandmax() == 2147483647.

The salt created will be 128 bits in
length, padded to 132 bits and then
expressed in 22 base64 characters.
(CRYPT_BLOWFISH only uses 128 bits for
the salt, even though there are 132
bits in 22 base64 characters. If you
examine the CRYPT_BLOWFISH input and
output, you can see that it ignores
the last four bits on input, and sets
them to zero on output.)

Note that the high-order bits of the
four 32-bit dwords returned by
mt_rand() will always be zero (since
mt_getrandmax == 2^31), so only 124 of
the 128 bits will be pseudorandom. I
found that acceptable for my
application.

I tested my server, and indeed mt_getrandmax() returns 2147483647. I tried poking around the documentation to understand what the above code really does–the pack() code N4 is for a 32-bit string (big endian byte order??) repeated 4 times… which I assume is why there’s 4 mt_rand() arguments.

What I don’t understand is why he replaces + with . and the purpose of 22 base64 characters (not that I fully understand what base64 is.)

It was recommended that I look into openssl_random_pseudo_bytes() for my random salt generation, as the previous method I was looking at was limiting itself to just 1234567890abcdefghijklmnopqrstuvwxyz.

Supposedly there was a bug pre 5.3.4 causing openssl_random_pseudo_bytes() to run painfully slow, occassionally causing timeout errors. I’m not sure if I should try to use openssl_random_pseudo_bytes() with Crypt() or something like the above method using mt_rand() and pack().

I’m trying to understand more how all these elements work, and what they are doing conceptually–rather than just using one without understanding it to achieve my goal; I’m trying to learn 😛

Can someone help me understand the different elements at work here, or at least direct me to a knowledge base where I can read about it? I think the most eluding component is understanding the different formats/terminology (base64, ascii, hexdec, bit, byte, etc.) but also in the end, how to achieve a fairly secure salt for use with my passwords.

How to&Answers:

Let me start of by saying that there is nothing special about a salt from the standpoint of generation. It’s just another random string. It’s special in how it’s used, but not generated.

Your specific questions

  1. Why does he replace + with .?

    I have no idea. Perhaps it’s because the + character may be confused with a space in urls. But a salt should never be in a url, so that’s likely not it.

  2. What does base64/hexdec do:

    Base64 converts a raw byte stream (each byte having values from 0 to 255) into a base 64 representation. There are plenty of resources on it, so it’s not worth going deep into. Read the wikipedia article for more information.

    hexdec converts a hex number (a-f0-9) into a decimal one. It converts from base 16 to base 10 (just another way of representing numbers).

  3. What is a bit and byte:

    A bit is a single unit of information. It has 2 states, 0 or 1. A Byte is a series of 8 bits. So a byte can have 256 unique combinations. Read Wikipedia

  4. What is ascii

    It’s a character set. It represents a single printable character in a single 8-bit byte. Again, I’d suggest reading Wikipedia.

Salts in General

The goal of a good salt generation function is large entropy. That means that the number of possible outputs is as large as possible. So any method should produce a large set of results.

Now, you need to define what are acceptable characters for the salt (since you’ll need to store the salt to verify the hash). The best possible salts are full-byte numbers and not just displayable characters. Now, you won’t be able to display this in a meaningful manor, but you don’t need to display it. Plus, for storage, you could always use base64_encode it.

Next, you need to choose how big you want the salt to be. The bigger the salt is, the better. A 32 character salt is acceptable, but a 128 character salt is better. The size of the salt, and the number of options per character will dictate the number of possibilities there are. Some common combinations:

Hex, 32 characters: 2e38 possibilities
Hex, 128 characters: 1e154 possibilities
Full Byte, 32 characters: 1e77 possibilities
Full Byte, 128 characters: 1e308 possibilities

Now, you need to generate the salt. The key is to do as many random calls as necessary to fill out the entropy. You can do this a few ways:

  • System Dependent (only works on *nix but best entropy):

    $f = fopen('/dev/urandom', 'r');
    $seed = fgets($f, $characters); // note that this will always return full bytes
    fclose($f);
    
  • Library dependent (good, but requires OpenSSL to be installed)

    $seed = openssl_random_pseudo_bytes($characters);
    
  • fallback

    $seed = '';
    for ($i = 0; $i < $characters; $i++) {
        $seed .= chr(mt_rand(0, 255));
    }
    

Now, you need to convert it into the desired output format.

  • Hex (a-f0-9):

    $out = '';
    for ($i = 0, $len = strlen($seed); $i < $len; $i++) {
        $num = ord($seed);
        $out .= dechex(floor($num / 16)) . dechex($num % 16);
    }
    
  • Base36 (a-z0-9):

    $out = '';
    for ($i = 0, $len = strlen($seed); $i < $len; $i++) {
        $num = ord($seed);
        $out .= base_convert($num, 10, 36);
    }
    
  • Base64 (a-zA-Z0-9+=):

    $out = base64_encode($seed);
    
  • Full Byte:

    Nothing is necessary since it’s already in this format.