DEV Community

Serguey Asael Shinder
Serguey Asael Shinder

Posted on

Your Password Check Stopped Reading After Seventy Two Bytes

The login service was slow,
so you added a cache.

When a user signs in successfully,
remember it for a while.

Next time,
check the cache first.

You needed a key
that nobody could reverse,
so you hashed the user ID,
the username and the password together,
with bcrypt,
because bcrypt is what you use
for passwords.

It looked careful.

It passed every test.

Months later,
a tester signed in
to a service account
with a long, descriptive username
and the wrong password.

It let him in.

Here is the mechanism.

bcrypt only reads
the first seventy two bytes
of whatever you give it.

Not characters.

Bytes.

Everything after that
is ignored,
and many libraries ignore it
without a word.

Your input started with the user ID,
then the username.

Put a long enough name in front,
and the password
falls off the end.

The hash is the same
whatever the password is.

The cache says yes.

This is not a thought experiment.

In 2024 a large identity provider
disclosed exactly this.

The cache key was bcrypt
over user ID, username and password,
and for usernames of fifty two characters or more,
a cached login
could succeed with any password.

The same limit bites
in quieter ways.

A passphrase of eighty characters
is only checked to seventy two.

A password in emoji
or Cyrillic
uses two to four bytes per character,
so it reaches the limit sooner.

Some newer library versions
now throw an error
instead of truncating.

Many still do not.

So, three rules.

Never build a bcrypt input
by gluing a secret
onto something else.

For a cache key or a lookup,
use HMAC with SHA-256
and a server secret.

It reads the whole input,
and it is fast,
which is what a cache wants.

For long passwords,
pre-hash with HMAC,
encode the result as text,
and give bcrypt that.

Or move to Argon2id,
which has no such limit.

And test the edge on purpose.

A seventy three byte input
that differs only at the end
must not verify.

A hash function
you did not read the limits of
is not checking what you think.

It is checking the first part.

– Serguey Asael Shinder

Top comments (0)