The perils of using PHP crypt()

Using PHP crypt() to create encrypted passwords can be a troublesome undertaking. Learn more about this function and some common sources of errors.

The PHP crypt() function is a handy tool for encrypting passwords and other data requiring comparison of encrypted strings but not actual decryption. However, selecting an appropriate encryption salt can be tricky and requires careful consideration to ensure proper function. Read on to learn more about crypt() and some of the potential traps you can run into while using it.

An overview of crypt()
The PHP crypt() function supports various encryption types, assuming that the system you are working on has them installed and they are supported. The most frequently used of these are DES and MD5. Extended DES and Blowfish are also supported, and because PHP crypt() uses the crypt functionality on your system, this same function can potentially be used with other algorithms. The command has the basic format:
$encr_password = crypt($password, $salt);

Crypt() provides a means of using hashing algorithms for encrypting strings. It is frequently used for concealing passwords because it offers one-way functionality. Once you have generated a string with crypt(), the result is merely a hashed representation of the original data and can therefore not be decrypted.

This does not mean that a string encrypted using crypt() cannot be discerned. In fact, authenticating passwords for your users depends on being able to recreate the exact hash result. In the case of dictionary cracks and other attacks of that type, a selection of words are encrypted using the same algorithm and salt as the original passwords, then compared with your data in the hopes that a hashed string matches one found in your password field.

This is why creating an appropriate salt to use for encryption is so important. If two users have selected the same password and you’re using a static salt, the two hashed strings will be identical, giving the attacker access to both accounts if only one of the passwords has been cracked.

Tales from the crypt()
Determining what type of encryption algorithm will be used by crypt() is important for knowing what kind of salt to provide. Depending on what your system supports, various salts will dictate what method is used.

The crypt() command accepts two parameters. The first is the password string you are encrypting. The second is an optional parameter containing the salt to use. If you do not include a salt, one will be generated for you. The salt is used to seed the encryption algorithm and dictates its results.

To determine what your system supports, you can check the values of constants defined by PHP. The constant CRYPT_SALT_LENGTH will display the expected length of the salt string. DES accepts a two-character salt. MD5 accepts 12 characters. You can also check to see whether any of the following flags are set: CRYPT_STD_DES, CRYPT_EXT_DES, CRYPT_MD5, CRYPT_BLOWFISH. To do this, issue a command such as:
echo CRYPT_MD5

A numeral 1 indicates it is supported; a 0 indicates that it is not.

If the relevant types of encryption are supported by your system, you can let crypt() know which to use by the salt you provide:
  • As of PHP 4.1, if no salt is provided, crypt() will use MD5 encryption and randomly generate a 12-character salt.
  • Older versions of PHP will use the first two characters of the password string as a salt if none is provided, and it uses DES-based encryption.
  • A salt of nine or more characters will use extended DES.
  • Use $1$ to start a 12-character salt to use MD5. Note that the last character will always be $, and extraneous characters will be ignored. An example looks like this: $1$blahblah$.
  • Use $2$ to start a 16-character salt to use Blowfish.
  • If anything not matching one of the above requirements is used as a salt or the relevant encryption type is not supported, the first two characters will be taken and DES will be used.

The salt that finally gets used is prepended to the resulting hash string. Once a user has an encrypted password in your system, you can authenticate that user by retrieving his or her stored password and using the salt to crypt() the entered password. If the resulting hash strings match, the user has entered the password correctly.

The crypt() function can be very particular about the way it operates. Here are some additional pitfalls to look out for:
  • With DES, if you do not provide a salt, two users with the same password will result in the same hash string.
  • DES uses only the first eight characters of a password when creating a hash. Other characters are disregarded.
  • With MD5, you must consistently use single or double quotes when defining a salt, even when defining it as a variable for use when issuing the crypt() command. The same salt will produce different results if quotes are not used consistently.

Seasoned to taste
As you can see, getting crypt() to use the desired encryption method can be a tricky business, and it depends on what your system will support and the directive that is inferred from the salt you have provided. Beware of the pitfalls we've discussed here, which may cause crypt() to function unexpectedly, especially when upgrading or changing systems.

Pass the salt
Share your algorithms for generating random salts in the discussion below.


Editor's Picks

Free Newsletters, In your Inbox