Password hash contained '\x00' in middle, resulting in ValueError from bcrypt.hashpw I have some code that accepts a password in string format, and hash it with SHA3-512 before passing it to bcrypt for hashing. However, by some coincidence, I found a test case that produces a hash that contains '\x00' in the middle of the hash result, as seen below:
password_str = 'tes15!tes15!tes15!tes15!tes15!tes15!tes15!tes15!tes15!tes15!tes15!tes15!.'
password_bytes = password_str.encode('utf-8')
hashed = hashlib.sha3_512(password_bytes).digest()

The hashing result that contains '\x00' in the middle of the hash:
b'u~&quot;\x98\xac\xc8E2eV\xbb\x8e#}\x92R\xdc\xa2\xab\xab\xcb\x8d.~\x9f\x82a\xbf\xec]k\xdb\xc55\x1d\xa4\x00\xe8\x03\x94\xb0\x91\x14\xf0\x9ec\x9a\x9ay\xfeP\xe3\x07J\x00\xb5\xbd\xba\xcb(\xf5\xdb\xab\x1a'

As we can see, there is an '\x00' found in the middle of the hash, but not at the end. bcrypt detects this a ValueError exception:
if b&quot;\x00&quot; in password:
    raise ValueError(&quot;password may not contain NUL bytes&quot;)

I understand that '\x00' is a reserved character and possible attack vector due to hash-length extension attacks. However, I do not think that I have done anything outside the ordinary. Is there a best practice that sanitizes the hash prior to passing it to bcrypt that I am missing, or is there another type of byte-encoding that should be used with passwords?
It would be weird to reject a normal password just because it's derived hash conflicts with the auth library used.. Any help or advice is greatly appreciated. Thank you!
EDIT: After posting, I found some issues on bcrypt that describes my problem
https://github.com/pyca/bcrypt/issues/55
https://github.com/pyca/bcrypt/pull/57
You should not be hashing it before passing to bcrypt, which is designed to do the hashing and key-stretching work itself.
It's choking on the hash result because it's expecting a redundant, mushy, ASCII (or UTF-8), not-rigorous, user-entered string.
Generally speaking, hashing things that might be untrustworthy is good to avoid various numeric vulnerabilities (e.g. the internal SHA-512 ops in Curve25519 to sanitize things) -- but, in this case, you can just trust bcrypt; it's designed to work safely and well when fed a lame, user-created password (and a proper, randomly-generated salt ).

 On further contemplation, though, I believe that the behavior of pyca/bcrypt is actually incorrect here. You are supposed to use a cryptographic hash function to convert passwords which are more than 72 bytes (576 bits) into a size that bcrypt can accept. [This is only for pigeonholing purposes; it IS still designed to safely stretch cryptographically mediocre passwords.]
Looking at the ticket on their tracker, and the changeset which they affirmed as having &quot;fixed&quot; it, it appears that the authors did not actually fix their implementation's oddball behavior, but instead merely updated their documentation to recommend the following workaround:

a common approach is to hash a password with a cryptographic hash (such as sha256) and then base64 encode it to prevent NULL byte problems…

This seems sloppy and bizarre to me; it will, for instance, limit such digests' output to, at most, around 408 bits (with base85 encoding), thus truncating &gt;72-byte passwords to just 51 bytes. I would be very interested to see the opinions of more experienced cryptographers on this.
This doesn't negate the first half of this answer, but it's something to keep in mind. Obviously, even with the workaround, the scheme is still quite secure (the best cryptographic schemes are designed to survive some slip-ups in their implementations); it just doesn't seem completely correct to be applying such arbitrary truncations to the user's password, above and beyond what the author of bcrypt itself applied.

You might want to consider bouncing that 72-byte limit back to your users, if you would otherwise have to truncate longer ones by a minimum of 30% to comply with pyca's implementation quirks. (I'm sure that those who care to max out their password's entropy and are hitting that 72-byte limit would, if they looked into how the sausage is made, ultimately prefer the former.)