ASVS V6.2.8 requires that your application checks the password exactly as the user submitted it, with no truncation, case changes or other modification. It is a Level 1 requirement.
This is a subtle requirement, because the bug is invisible. Everything works in testing, users sign in, and yet the stored credential is weaker than the user believes.
Why it matters
Silent changes shrink the keyspace. If you truncate at 72 bytes, anything after that is not protecting the account. Lowercasing makes Correct Horse and correct horse the same secret. Trimming whitespace can lock out users whose password legitimately ends in a space.
There are three common ways a password is quietly altered.
Truncation: bcrypt only reads the first 72 bytes. Some wrappers pass through longer strings without warning, so two different long passwords are equivalent. An attacker who learns the first 72 bytes can sign in, which defeats the idea of a long passphrase.
Case folding: converting to lowercase halves the effective alphabet for each letter and merges distinct passwords. It is sometimes added to help users with caps lock problems, but it lowers security for everyone.
Trimming and sanitizing: removing spaces, stripping quotes, or escaping characters for SQL or HTML changes the secret. Passwords are never output or interpolated, so the escaping is not needed, and parameterized queries keep them safe in storage.
How to implement it
Pass the raw value to a password hashing function. If your algorithm has a hard input limit, deal with it explicitly instead of letting a library cut the input.
from argon2 import PasswordHasher
import unicodedata
ph = PasswordHasher()
def hash_pw(raw: str) -> str:
# Optional: Unicode NFKC normalization is allowed, but apply it the same way at set and verify.
return ph.hash(unicodedata.normalize("NFKC", raw))
def check_pw(stored: str, raw: str) -> bool:
try:
return ph.verify(stored, unicodedata.normalize("NFKC", raw))
except Exception:
return False
Argon2 has no 72 byte limit. With bcrypt, either pre hash with a documented scheme or reject longer inputs with a clear error rather than cutting them.
Follow one rule: hash what you receive, verify what you receive. Do not run the password through any validation function that returns a changed copy.
If you choose to normalize Unicode, do it deliberately. NFKC normalization means that visually identical forms of an accented letter match. Document it, apply it at every entry point, and keep it identical in the mobile app and the web service.
For bcrypt, you have two options. Choose a different algorithm such as Argon2id or scrypt, which accepts long input. Or pre hash with SHA 256 and encode the digest as base64 before bcrypt, applying the same step at set and verify. Reject inputs over your maximum with an error rather than cutting them.
# bcrypt with explicit length guard
MAX_BYTES = 72
def hash_password(raw: str) -> bytes:
data = raw.encode("utf-8")
if len(data) > MAX_BYTES:
raise ValueError("password too long for this scheme")
return bcrypt.hashpw(data, bcrypt.gensalt(rounds=12))
A better answer in new systems is to use Argon2id and avoid the limit altogether.
How to test it
Create an account with a 100 character password. Sign in with the first 72 characters only. It must fail. Do the same with a trailing space removed and with the case changed. All must fail.
Create three automated tests. Test one sets a password of 100 characters that differ only after character 72, then confirms that the other variant fails. Test two sets a password with mixed case and confirms that the all lowercase form fails. Test three uses a password with leading and trailing spaces and confirms that the trimmed form fails.
Then read the code path from the HTTP request to the hash function and look for calls such as strip, lower, substring, replace and sanitize. Any hit is a candidate for a bug.
Common mistakes
-
Using
.strip()or.lower()on the password. -
Letting bcrypt truncate quietly.
-
Applying normalization at signup but not at login.
-
Applying a global input sanitizer middleware that cleans every string field, including the password.
-
Using different normalization on the web and mobile clients.
-
Truncating in the database column when storing a pre hash.
Try it yourself
Click through it at your own pace, or open it full screen.
Quick check
Is Unicode normalization a modification?
It is acceptable if you apply it consistently at set and verify time and document it. Truncation and case folding are not.