8.9 Performing Password-Based Authentication with crypt( )

8.9.1 Problem

You need to use the standard Unix crypt( ) function for password-based authentication.

8.9.2 Solution

The standard Unix crypt( ) function typically uses a weak one-way algorithm to perform its encryption, which is usually also slow and insecure. You should, therefore, use crypt( ) only for compatibility reasons.

Despite this limitation, you might want to use crypt( ) for compatibility purposes. If so, to encrypt a password, choose a random salt and call crypt( ) with the plaintext password and the chosen salt. To verify a password encrypted with crypt( ), encrypt the plaintext password using the already encrypted password as the salt, then compare the result with the already encrypted password. If they match, the password is correct.

8.9.3 Discussion

What we are doing here isn't really encrypting a password. Actually, we are creating a password validator. We use the term encryption because it is in common use and is a more concise way to explain the process.

The crypt( ) function is normally found in use only on older Unix systems that still exclusively use the /etc/passwd file for storing user information. Modern Unix systems typically use stronger algorithms and alternate storage methods for user information, such as the Lightweight Directory Access Protocol (LDAP), Kerberos (see Recipe 8.13), NIS, or some other type of directory service.

The traditional implementation of crypt( ) uses DES (see Recipe 5.2 for a discussion of symmetric ciphers, including DES) to perform its encryption. DES is a symmetric cipher, which essentially means that if you have the key used to encrypt, you can decrypt the encrypted data. To make the function one-way, crypt( ) encrypts the key with itself.[1]

[1] Some older versions encrypt a string of zeros instead.

The DES algorithm requires a salt, which crypt( ) limits to 12 bits. It also prepends the salt to the resulting ciphertext, which is base64-encoded. DES is a weak block cipher to start, and the crypt( ) function traditionally limits passwords to a single block, which serves to further weaken its capabilities because the block size is 64 bits, or 8 bytes.

Because DES is a weak cipher and crypt( ) limits the plaintext to a single DES block, we strongly recommend against using crypt( ) in new authentication systems. You should use it only if you have a need to maintain compatibility with an older system that uses it.

Encrypting a password with crypt( ) is a simple operation, but programmers often get it wrong. The most common mistake is to use the plaintext password as the salt, but recall that crypt( ) stores the salt as the first two bytes of its result. Because passwords are limited to eight bytes, using the plaintext password as the salt reveals at least a quarter of the password and makes dictionary attacks easier.

The crypt( ) function has the following signature:

char *crypt(const char *key, const char *salt);

This function has the following arguments:


Password to encrypt.


Buffer containing the salt to use. Remember that crypt( ) will use only 12 bits for the salt, so it will use only the first two bytes of this buffer; passing in a larger salt will have no effect. For maximum compatibility, the salt should contain only alphanumeric characters, a period, or a forward slash.

The following function, spc_crypt_encrypt( ), will generate a suitable random salt and return the result from calling crypt( ) with the password and generated salt. The crypt( ) function returns a pointer to a statically allocated buffer, so you should not call crypt( ) more than once without using the results from earlier calls because the data returned from earlier calls will be overwritten.

#include <string.h>
#include <unistd.h>
char *spc_crypt_encrypt(const char *password) {
  char salt[3];
  static char *choices = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  salt[0] = choices[spc_rand_range(0, strlen(choices) - 1)];
  salt[1] = choices[spc_rand_range(0, strlen(choices) - 1)];
  salt[2] = 0;
  return crypt(password, salt);

Verifying a password encrypted with crypt( ) involves encrypting the plaintext password to be verified and comparing it with the already encrypted password, which would normally be obtained from the passwd structure returned by getpwnam( ) or getpwuid( ). (See Recipe 8.2.)

Recall that crypt( ) stores the salt as the first two bytes of its result. For purposes of verification, you will not want to generate a random salt. Instead, you should use the already encrypted password as the salt.

You can use the following function, spc_crypt_verify( ), to verify a password; however, we're really only providing an example of how crypt( ) should be called to verify a password. It does little more than call crypt( ) and compare its result with the encrypted password.

#include <string.h>
#include <unistd.h>
int spc_crypt_verify(const char *plain_password, const char *cipher_password) {
  return !strcmp(cipher_password, crypt(plain_password, cipher_password));

8.9.4 See Also

Recipe 5.2, Recipe 8.2, Recipe 8.13