Description
`bcrypt::verify(password, hash)` and `HashParts::from_str(hash)` panic in
`str::slice_error_fail` when given a 60-byte `&str` containing a multi-byte
UTF-8 character at certain byte positions.
## Impact
Any Rust code that calls `bcrypt::verify` (or `HashParts::from_str`) with an
attacker-controlled hash string will panic. The `bcrypt` crate is
`#![forbid(unsafe_code)]`, so this is limited to a denial-of-service and
cannot lead to memory corruption.
Realistic attack contexts include:
- Rust authentication services reading hashes from a database that was
previously compromised via, e.g., SQL injection. The attacker can then
crash the service on every login attempt against the tampered account.
- CLI tools accepting hashes from stdin or command-line arguments.
- Password managers or vault services loading hashes from untrusted
configuration sources.
## Root cause
`split_hash` performed five `&str` slicing operations on the input hash:
`&hash[1..3]`, `&hash[4..6]`, `&hash[7..]`, `&salt_and_hash[..22]`, and
`&salt_and_hash[22..]`. None of these were char-boundary-checked. Any input
where a multi-byte UTF-8 character spanned one of those byte positions
caused a panic.
This is a regression of the fix originally shipped in 2021 for issue #62
(commit `0833509`). The regression was introduced in the parser rewrite in
PR #95 (commit `e9a8394`, released as 0.19.0).
The pre-existing regression test `does_no_error_on_char_boundary_splitting`
was not removed, but was silently rendered ineffective by the new
`bytes[0] != b'$'` guard, which rejected its input earlier and prevented
it from reaching the buggy slices — leaving CI green through the regression.
## Fix
`split_hash` now rejects any hash string containing non-ASCII bytes up
front. A valid bcrypt hash is always exactly 60 ASCII bytes, so this
closes the entire class of byte-boundary panics rather than guarding each
slice individually.
The fix was merged in PR #103 and released as `bcrypt 0.19.2` on 2026-06-20.
## Downstream impact
`pyca/bcrypt` (which depended on `bcrypt 0.19.1`) is **not** affected. Its
Python-side wrapper performs its own byte-level salt parsing before
invoking `bcrypt::hash_with_salt`, and never reaches the buggy code path
in `split_hash` or `verify`.
Affected packages
| Ecosystem | Package | Affected | Fixed | Record |
|---|---|---|---|---|
| crates.io | bcrypt | >= 0.19.0, < 0.19.2 | 0.19.2 | RUSTSEC-2026-0199 |
References
- https://crates.io/crates/bcrypt package
- https://rustsec.org/advisories/RUSTSEC-2026-0199.html advisory
- https://github.com/Keats/rust-bcrypt/pull/103 web
- https://github.com/Keats/rust-bcrypt/issues/62 report
- https://github.com/Keats/rust-bcrypt/pull/95 web
- https://github.com/Keats/rust-bcrypt/commit/f5f1ee2862c1198a85afe3c2f8cd80835162b7e9 web