Bug-Description When using BCryptPasswordEncoder in kotlin I have encountered a bug where two obviously different strings match positively to each other. And this is not only bound to these two strings, but rather to all JSON Web token I tested it with. What in all world is happening here???

To Reproduce Execute following sample code: My current setup is springframework.boot version 3.1.5 using the spring-boot-starter-security library

Expected behavior Since Strings v1 and v2 are different, I expect the BCryptPasswordEncoder::matches()function to return false, but instead it returned true. How come??

Sample

val v1 = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMjEzNzZkOC01YjQ3LTQxNTUtYmE1ZS1jZjI5OWM4YWQ2Y2EiLCJ3a19yb2xlcyI6IlJPTEVfUEFUSUVOVCIsIndrX3Rva2VuX3R5cGUiOiJQQVRJRU5UX1JFRlJFU0giLCJleHAiOjE3MzA2NzE3NTd9.LB1ZmzHlx6guH7yelW8wzVBmCuna7v2gRA1jJsj1QL1VqmDx4-FLrGcZXLtV50AeN3Fw0UE1fDbdHatC75_liE6w4wvTWS5fC88nn76S0rV_5Jq_Gqsbd0wbwXFiESvWzzYUZfxp6b-pOgJu031YQeznM2K7CTeU32XXbIrEf-XGEt1h_XQrHR6WP_O5n8a1eySMlk53QOMKK0YKbnlZDtLD05XfDJwT3ULHYcqpjxpGRLgZRbnAzm09sb4dCuHoAA9BIp8HEZ9MzO22F28qStO6Suz70teuLdAz3eqA7D0pMYeusvBWY6GTgfJsknKjaTq9uXsMgwGNinoTppNWA_2ka3wNWTmnKh6osulGOMsAM85nrYDfsxLhP-CMdKPRyRWsfshMY8z1f4ayxtnmEaCOOITNWsXULvsr-0ljDNXuFCp2syhFeylwJLrGU14_isBlSltNh02zEvcqKgfdFEs65COoZjfFlX94rrbPtlPjS1pf9YBdqWX1QUZpFkvk"
val v2 = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMjEzNzZkOC01YjQ3LTQxNTUtYmE1ZS1jZjI5OWM4YWQ2Y2EiLCJ3a19yb2xlcyI6IlJPTEVfUEFUSUVOVCIsIndrX3Rva2VuX3R5cGUiOiJQQVRJRU5UX1JFRlJFU0giLCJleHAiOjE3MzA2NzIxMDZ9.KIsfb6FFCljmlcI70YpbBtsNKBbBf3Hvs323qA6Gc1ttZYmUv3ah-gziIOg43VJpqvMpq0Y2n3x9LmN_8eZdD--iCA4U_MNqdk05E53drDOFJLQx4iBcH7e47riUWKwv1gH8jshm0l3Z8JGu5DrghuvsKsS4RxfYOm9Ll1Ldeamw9FPaURv2sLfcXt1fVOeAzeraIDy-hPAHtxVyRHi3guMrywwg0R_XoUAhhHaYQqRMZAzSE0ETFiqi2TRv64HKhqPV2PuFXy8NYWavyCe1S4XGP-rQ36pls2UF4hQP9l8JacXwmrwxuJW--8ko1oQQFIOrnVbwRxXU0JMrFTS1B0h-_o3OmkiOYaLv0LVm6JpUmmzAbovhJBOoa073F6my4pQ0Qv1apoUVvpVlzfe-DQTPGdshROhFhK5w7KMYdCW1usEGJLQGVXwHww1g20L_YCIP4VzuaefsIlDsCGXzmnKtOVFMrRedTuA5yDOiRgX9ccXmEm15bOc0Swm1yXBY"

val pwEncoder = BCryptPasswordEncoder()
log.info("matches???? ${pwEncoder.matches(v1, pwEncoder.encode(v2))}")

Thanks in advance for the help, I am really questioning my sanity right now.

Comment From: ngocnhan-tran1996

@pH-Valiu

Quote from Bcrypt Wikipedia

Maximum password length bcrypt has a maximum password length of 72 bytes

It also attach in document Spring - Section Bcrypt

BCryptPasswordEncoder

Just use a text like below example, you will see same result

        var v1="You_Can_See_This_Text_Is_Too_Long_And_It_Has_Length_72_So_It_Always_True";
        var v2="You_Can_See_This_Text_Is_Too_Long_And_It_Has_Length_72_So_It_Always_True_When_Comparing";
        var pwEncoder = new BCryptPasswordEncoder();
        System.out.println(pwEncoder.matches(v1, pwEncoder.encode(v2))); // output: true

Comment From: pH-Valiu

Oh my good, thank you very much 🙏 Can you recommend me any alternatives that would bring me the desired functionality in spring?

Comment From: ngocnhan-tran1996

I dont know any alternatives function in spring

Comment From: philipwhiuk

@jzheaux Can we re-open this one - see https://n0rdy.foo/posts/20250121/okta-bcrypt-lessons-for-better-apis/

Spring shouldn't silently accept invalid input and arbitrarily truncate.

Comment From: k98kurz

This is a pretty huge security flaw. If you do not want to be bound by the 72 max length, you can use a recursive method that cuts the input into 72-byte chunks, hashes each chunk to get the 24-byte digest (since all sub-hashes will have the same cost and salt), concatenate the 24-byte digests together and feed it back into the recursive method until it is reduced to a single 24-byte digest, then move to the final encoding steps. Or at least, that should be an option instead of just leaving any application that uses this vulnerable or broken.

Comment From: leonard84

@kostya05983, then it wouldn't be BCrypt anymore, and you couldn't use another implementation interchangeably. That being said, I also agree it should fail instead of silently truncating the password.

Comment From: RobMaskell

From the link, just to put the possible bad front and center

"This means that if the user had a username above 52 chars, any password would suffice to log in"

Not ideal if confirmed