Signature
crypt(#[\SensitiveParameter] string $string, string $salt): stringPurpose
crypt() produces a one-way hash of a string, using the traditional Unix crypt family of algorithms. The salt argument decides which algorithm runs. The function is mostly useful today for reading and verifying existing hashes, including ones created by non-PHP software. For new passwords, the manual recommends password_hash(), which chooses a suitable algorithm, cost and salt for you. Hashes produced by crypt() can also be checked with password_verify().
Signature
Parameters
$string: the text to hash. The manual warns that the function is not binary safe. Some algorithms also use only part of the input. Standard DES reads only the first eight characters, and Blowfish truncates the input to 72 bytes.$salt: required since PHP 8.0.0. Its format selects the hash type. There is no default.
The supported salt formats include:
- Standard DES: two characters from
./0-9A-Za-z. - Extended DES: an underscore followed by four characters of iteration count and four of salt.
- MD5: starts with
$1$. - Blowfish:
$2a$,$2x$or$2y$, a two-digit cost from 04 to 31,$, then 22 characters from./0-9A-Za-z. Use$2y$for new hashes. - SHA-256 and SHA-512: start with
$5$and$6$, optionally followed byrounds=<N>$.
The manual states that none of these algorithms is suitable for hashing new passwords. The fast ones resist offline cracking poorly, and the SHA variants take time that grows with the square of the input length.
Return value and failure
On success you get the hashed string. The salt is embedded in the result, so the whole result can be passed back to crypt() as the salt when verifying. On failure crypt() does not throw. It returns a string shorter than 13 characters that is guaranteed to differ from the salt, typically a marker such as *0. Check for this if the salt comes from untrusted or stored data.
Example 1: a Blowfish hash
A fixed salt makes the result repeatable. A Blowfish hash is 60 characters long and begins with the 29-character salt portion.
$salt = '$2y$04$abcdefghijklmnopqrstuu';
$hash = crypt('secret', $salt);
echo json_encode([
strlen($hash),
str_starts_with($hash, $salt),
$hash === crypt('secret', $salt),
password_verify('secret', $hash),
]);[60,true,true,true]
The cost of 04 is the lowest allowed value. It keeps the example quick, but it is far too low for real use.
Example 2: verifying with hash_equals()
To verify, hash the input using the stored hash as the salt, then compare with hash_equals(). A plain === comparison can leak timing information.
function check(string $input, string $stored): bool
{
return hash_equals($stored, crypt($input, $stored));
}
$stored = crypt('correct horse', '$2y$04$abcdefghijklmnopqrstuu');
echo json_encode([check('correct horse', $stored), check('wrong', $stored)]);[true,false]
Example 3: truncation and failure
Standard DES ignores everything after the eighth character, and it returns the salt as the first two characters of its 13-character output. A malformed Blowfish salt produces a short failure string.
$a = crypt('12345678abc', 'ab');
$b = crypt('12345678xyz', 'ab');
$bad = crypt('secret', '$2y$04$short');
echo json_encode([
'sameFirstEight' => $a === $b,
'desLength' => strlen($a),
'saltPrefix' => substr($a, 0, 2),
'failureLooksShort' => strlen($bad) < 13 && $bad !== '$2y$04$short',
]);{"sameFirstEight":true,"desLength":13,"saltPrefix":"ab","failureLooksShort":true}Edge cases
- Two inputs that share the same first eight characters give identical standard DES hashes under the same salt. Inputs that share the same first 72 bytes do the same under Blowfish.
- Invalid salt characters make DES and extended DES fail. For Blowfish, the manual says characters outside the allowed alphabet give a zero-length string, and a cost outside 04-31 also fails.
- SHA salts with
rounds=values outside 1000 to 999,999,999 are clamped to the nearest limit rather than rejected. - Never pass unbounded user input to the SHA variants, because the running time grows quickly with length.
- There is no decrypt function. Hashing is one-way.
Common mistakes
- Using
crypt()to create new password hashes instead ofpassword_hash(). - Omitting the salt. This is a
TypeErrorin PHP 8, because the argument is no longer optional. - Comparing hashes with
==or===rather thanhash_equals(). - Treating the failure marker as a valid hash and storing it.
- Assuming a long password is fully used. DES reads eight characters and Blowfish reads 72 bytes.
- Using a predictable or reused salt, which defeats the purpose of salting.
Related functions
password_hash()creates a modern password hash with an automatically generated salt.password_verify()checks a password against a hash, includingcrypt()hashes.hash_equals()compares strings in constant time.
Recap
crypt() hashes a string with an algorithm chosen by the salt format, and it returns a short marker string on failure. Verify by passing the stored hash as the salt and comparing with hash_equals(). For anything new, prefer password_hash().
Tested examples
$salt = '$2y$04$abcdefghijklmnopqrstuu';
$hash = crypt('secret', $salt);
echo json_encode([
strlen($hash),
str_starts_with($hash, $salt),
$hash === crypt('secret', $salt),
password_verify('secret', $hash),
]);
[60,true,true,true]
function check(string $input, string $stored): bool
{
return hash_equals($stored, crypt($input, $stored));
}
$stored = crypt('correct horse', '$2y$04$abcdefghijklmnopqrstuu');
echo json_encode([check('correct horse', $stored), check('wrong', $stored)]);
[true,false]
$a = crypt('12345678abc', 'ab');
$b = crypt('12345678xyz', 'ab');
$bad = crypt('secret', '$2y$04$short');
echo json_encode([
'sameFirstEight' => $a === $b,
'desLength' => strlen($a),
'saltPrefix' => substr($a, 0, 2),
'failureLooksShort' => strlen($bad) < 13 && $bad !== '$2y$04$short',
]);
{"sameFirstEight":true,"desLength":13,"saltPrefix":"ab","failureLooksShort":true}