Okta, bcrypt, and 52+ Character Usernames
On 30 October 2024 Okta published the security advisory “Okta AD/LDAP Delegated Authentication - Username Above 52 Characters Security Advisory”. While they don’t detail why this vulnerability happens, they do provide enough information to put the pieces together.
The short version: they were using bcrypt, which has a known limitation of only paying attention to the first 72 bytes.
The Bcrypt algorithm was used to generate the cache key where we hash a combined string of userId + username + password.
– Okta
In order to make sure each has will be treated as unique by bcrypt, the entire length of userId + username + password
must be 72 bytes or less. It appears they were doing nothing to enforce that limit. If the userId
is 20 characters, that leaves you with only 52 characters that bcrypt will pay attention to. That pushes the password past the 72 character limit, into the ignored zone.
Here is what that might look like in PHP.
$userId = '7788990011223344551';
$short_username = 'zerocool95';
$long_username = 'zerocool95-Y3Jhc2ggb3ZlcnJpZGUgaXMgcmVhbGx5IHplcm8gY29vbA';
$password = 'Mess with the best, die like the rest.';
echo "Using short username\n";
$combined = $userId . $short_username . $password;
$hash = password_hash(
$combined,
PASSWORD_BCRYPT,
[
'cost' => 12
]
);
echo $hash . "\n";
if ( password_verify( $combined, $hash ) ) {
echo "Correct user id, username, password combination\n";
} else {
echo "Incorrect combination\n";
}
$bad_combination = $userId . $short_username . 'not-my-password';
if ( password_verify( $bad_combination, $hash ) ) {
echo "Correct user id, username, password combination\n";
} else {
echo "Incorrect combination\n";
}
// Output
// $2y$12$OSlXjZirMYlaKtXqMTr1uePNIEEsxS4sDQHCpfg.vC/Aw9SBaEvBS
// Correct user id, username, password combination
// Incorrect combination
echo "\n---\n";
// Now try it with the long, 52+ characters, username
echo "Using LONG short username\n";
$combined = $userId . $long_username . $password;
$hash = password_hash(
$combined,
PASSWORD_BCRYPT,
[
'cost' => 12
]
);
echo $hash . "\n";
if ( password_verify( $combined, $hash ) ) {
echo "Correct user id, username, password combination\n";
} else {
echo "Incorrect combination\n";
}
$bad_combination = $userId . $long_username . 'not-my-password';
if ( password_verify( $bad_combination, $hash ) ) {
echo "Correct user id, username, password combination\n";
} else {
echo "Incorrect combination\n";
}
// Output
// $2y$12$p2xn6C0duRrnpjFQWTBCJe7hjlIq0GZHwmwRdAUgeOD30a5T85V/G
// Correct user id, username, password combination
// Correct user id, username, password combination
You can run that yourself at https://3v4l.org/b3Plj#v8.3.13. This is not how you want your password verification to happen. Providing an incorrect password should never work.
This problem can happen in any system that is using bcrypt. Even if the only thing you are using it for is to hash the password. Two passwords that are 80 characters long and have the first 72 in common are identical in the eyes of bcrypt.
There are a few different ways you can address this. They are in the order of my personal preference.
- Switch to a hash that doesn’t have a 72-byte limit ( Okta switched to PBKDF2 )
- Refuse to accept passwords longer than 72-bytes, and tell users that is the limit ( or less )
- Pre-hash the password using a secure hashing algorithm that does not have a length limit ( SHA-256 has been suggested as an option )
Be careful with that third option, mixing hashing algorithms, if done incorrectly can create new problems.