Search code examples
javaspringbcryptjbcrypt

Why is org.mindrot.JBCrypt saying Bad salt length here?


Example is worth a thousand words, hopefully. In case it doesn't, here're a couple tests to hash the plainText hello world using a salt seed static seed to be used in the first test and static seed to be usedd in the second test. Salt seed is used to generate a static salt that can be passed into BCrypt.hashpw(plainText, staticSalt) function. As you can see, the salt bytes, and salt byte strings are similar length but it is throwing an error. I know it's bad but I have my reasons for static salt, so please put your focus on the question.

org.mindrot.jbcrypt.BCrypt with JDK1.7 Test 1 - PlainText: "hello world", saltseed: "static seed to be used":

Salt bytes for "static seed to be used": [-30, -8, 86, -8, 6, -126, -64, -30, -82, -82, -104, -64, -8, -118, -64, 108, -82, -64, 14, -30, -82, -104]
Salt bytes string: 4vhW+AaCwOKurpjA+IrAbK7ADuKumA==, length: 32
complete salt: $2a$12$4vhW+AaCwOKurpjA+IrAbK7ADuKumA==
Exception in thread "main" java.lang.IllegalArgumentException: Bad salt length
at org.mindrot.jbcrypt.BCrypt.crypt_raw(BCrypt.java:619)
at org.mindrot.jbcrypt.BCrypt.hashpw(BCrypt.java:684)

org.springframework.security.crypto.bcrypt.BCrypt with JDK1.8 Test 1 - PlainText: "hello world", saltseed: "static seed to be used":

Salt bytes for "static seed to be used": [-30, -8, 86, -8, 6, -126, -64, -30, -82, -82, -104, -64, -8, -118, -64, 108, -82, -64, 14, -30, -82, -104]
Salt bytes string: 4vhW+AaCwOKurpjA+IrAbK7ADuKumA==, length: 32
complete salt: $2a$12$4vhW+AaCwOKurpjA+IrAbK7ADuKumA==
Plain text: hello world, Hash text: $2a$12$4vhWHrTxEMtyyv6wmpOtX.YYbTqHwHv/dxe

org.mindrot.jbcrypt.BCrypt with JDK1.7 Test 2 - PlainText: "hello world", saltseed: "static seed to be usedd":

Salt bytes for "static seed to be usedd": [85, 108, -73, 108, 111, -27, -32, 85, 19, 19, -4, -32, 108, -7, -32, -50, 19, -32, -125, 85, 19, -4]
Salt bytes string: VWy3bG/l4FUTE/zgbPngzhPgg1UT/A==, length: 32
complete salt: $2a$12$VWy3bG/l4FUTE/zgbPngzhPgg1UT/A==
Plain text: hello world, Hash text: $2a$12$VWy3bG/l4FUTE/zgbPngze9KDSXjF72NBMBNE6ZJk4StahyAhykgO

org.springframework.security.crypto.bcrypt.BCrypt with JDK1.8 Test 2 - PlainText: "hello world", saltseed: "static seed to be usedd":

Salt bytes for "static seed to be usedd": [85, 108, -73, 108, 111, -27, -32, 85, 19, 19, -4, -32, 108, -7, -32, -50, 19, -32, -125, 85, 19, -4]
Salt bytes string: VWy3bG/l4FUTE/zgbPngzhPgg1UT/A==, length: 32
complete salt: $2a$12$VWy3bG/l4FUTE/zgbPngzhPgg1UT/A==
Plain text: hello world, Hash text: $2a$12$VWy3bG/l4FUTE/zgbPngze9KDSXjF72NBMBNE6ZJk4StahyAhykgO

I've tried adding and deleting more letters and got successful hashes. I was kind of happy that the first String used in the JUnit test threw an error.

Thanks in advance.


Solution

  • I found the reason by looking at its implementation on a GitHub page... This implementation of BCrypt supports base64 dot(.) character instead of the standard plus(+) character.

    static private final byte index_64[] = {
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
            56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
            -1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
            7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
            -1, -1, -1, -1, -1, -1, 28, 29, 30,
            31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
            41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
            51, 52, 53, -1, -1, -1, -1, -1
    };
    

    "+" character has integer value of 43, thus returns a -1 from this array, and the function that is retrieving the salt bytes from salt breaks earlier leaving me with a 4 bytes salts. Even its implementation says it doesn't support standard base64 encoding string:

    /**
     * Decode a string encoded using bcrypt's base64 scheme to a
     * byte array. Note that this is *not* compatible with
     * the standard MIME-base64 encoding.
     */
    static byte[] decode_base64(String s, int maxolen)
            throws IllegalArgumentException {
    

    Switching from . to + gives me a different hashed from Spring on salts containing +. Not that familiar with shift and & to make the changes so going to look for the Spring-Security's implementation instead.

    EDIT: Just took a look at Spring's implementation. No wonder it didn't work... It is exactly the same except Spring continues to hash with a 4 bytes salt while jbcrypt throws an error, thus there's a bug in Spring whereas if you generate your own hash and it contains +, then the checkpw(plainText, hashedText) will return false because the generated salt part of the hashtedText is different from the user generated salt.