Password Hashing on Linux

Last update: 2025-08-08 11:46:18 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Password storage uses a one-way password-hashing function, not encryption. A verifier hashes a submitted password with the settings embedded in the stored value and compares the encoded results. Recovery of a plaintext password is not part of the design; an attacker who obtains the database instead tests candidate passwords offline. Thus, modern password hashing needs a per-password random salt and a deliberately expensive, supported algorithm.

crypt Encoded Hashes #

The historical crypt(3) interface accepts a password and a setting. Its modular format commonly looks like:

text
$id$parameters$salt$checksum

The identifier and parameter syntax select an algorithm; the salt makes equal passwords produce different stored values; the checksum is the verifier. The complete stored string is supplied as the setting during verification, so an application must preserve it exactly. Traditional DES crypt instead uses a two-character salt and is not suitable for new password hashes.

glibc’s former libcrypt documented $1$ (MD5), $5$ (SHA-256), and $6$ (SHA-512); the SHA formats accept rounds=N. Their historical compatibility does not make MD5 or DES suitable for new deployments. The available methods and accepted identifiers are implementation and build configuration details, not a portable POSIX password format.

crypt returns static storage and is unsafe for concurrent calls. crypt_r uses caller-provided struct crypt_data, which must be initialized as the implementation requires and must not be shared between threads. Check failure before comparison: implementations may return NULL or an invalid value. Use constant-time comparison when an application handles hash comparisons itself, and erase password material as soon as the API permits.

c
struct crypt_data data = {0};
char *candidate = crypt_r(password, stored_hash, &data);
if (candidate != NULL && constant_time_equal(candidate, stored_hash)) {
    /* authenticated */
}

The example describes verification mechanics, not a login architecture. PAM-aware programs should delegate authentication to PAM rather than read account databases or implement prompting and throttling themselves.

glibc and libxcrypt Compatibility #

crypt is an optional POSIX XSI interface. glibc removed its in-tree libcrypt in glibc 2.39; Linux distributions commonly provide libxcrypt instead. libxcrypt can provide libcrypt.so.1 with glibc-compatible symbol versions, allowing existing Linux binaries linked against glibc’s historical library to continue to load. A system configured without that compatibility installs a different ABI name, so build and runtime compatibility must be checked independently.

Besides crypt and crypt_r, libxcrypt provides Openwall-originated interfaces:

  • crypt_rn accepts an explicitly sized caller buffer;
  • crypt_ra allocates or resizes its working storage;
  • crypt_gensalt, crypt_gensalt_rn, and crypt_gensalt_ra generate settings and salts.

libxcrypt supports a configurable set of historical and modern schemes, including yescrypt on builds that enable it. Do not infer support from an identifier alone. Select the distribution’s supported password policy, generate salts with an operating-system cryptographic random source, and use the cost parameters documented for that scheme. Raising a cost affects every interactive login and must be capacity-tested; migration must retain verification support for existing hashes until each account has been rehashed after successful authentication.

Shadow Ownership #

/etc/passwd must remain readable so processes can map numeric IDs to account names. On shadow-based systems, its password field is commonly x, while the password hash and aging data live in /etc/shadow. A shadow record contains the account name, encoded hash or lock marker, last-change day, minimum and maximum ages, warning and inactivity periods, expiration day, and a reserved field.

The shadow file is privileged account data. Its ownership and mode are distribution policy, but ordinary users must not be able to read hashes. The shadow suite owns utilities such as passwd, chpasswd, pwconv, and pwunconv; code should not parse and rewrite /etc/shadow directly. NSS lookup APIs may read shadow data, but access controls still apply.

Authentication and Migration #

At login, a PAM-aware service starts a PAM transaction. A module such as pam_unix obtains credentials through the application’s conversation callback, reads the configured account data with the required privilege, invokes the configured hashing implementation, and reports success or failure to the PAM stack. Account aging, lock state, multifactor methods, directory authentication, and rate-limiting policy belong in that stack and its supporting services.

For a migration:

  1. configure a supported current algorithm and its cost in the account-management policy;
  2. keep legacy verification enabled only for the migration period;
  3. rehash at the next successful password change or verified login;
  4. expire or reset accounts that cannot complete the migration; and
  5. remove legacy algorithms after no stored hashes require them.

The libxcrypt interface documentation, crypt(3), and shadow(5) describe the platform-specific contracts.

Comments

Read or comment on GitHub (sign-in required to comment)