12

I've been given the task of creating a Login API for our project and I'm supposed to use PBKDF2 with HMACSHA256 as the PRF. The plain text password is hashed using MD5 and then fed into the PBKDF2 to generate a derived key. The problem is, I'm not able to get the same output as what the project documentation is telling me.

Here's the PBKDF2 Implementation in Java:

public class PBKDF2
{
    public static byte[] deriveKey( byte[] password, byte[] salt, int iterationCount, int dkLen )
        throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException
    {
        SecretKeySpec keyspec = new SecretKeySpec( password, "HmacSHA256" );
        Mac prf = Mac.getInstance( "HmacSHA256" );
        prf.init( keyspec );

        // Note: hLen, dkLen, l, r, T, F, etc. are horrible names for
        //       variables and functions in this day and age, but they
        //       reflect the terse symbols used in RFC 2898 to describe
        //       the PBKDF2 algorithm, which improves validation of the
        //       code vs. the RFC.
        //
        // dklen is expressed in bytes. (16 for a 128-bit key)

        int hLen = prf.getMacLength();   // 20 for SHA1
        int l = Math.max( dkLen, hLen); //  1 for 128bit (16-byte) keys
        int r = dkLen - (l-1)*hLen;      // 16 for 128bit (16-byte) keys
        byte T[] = new byte[l * hLen];
        int ti_offset = 0;
        for (int i = 1; i <= l; i++) {
            F( T, ti_offset, prf, salt, iterationCount, i );
            ti_offset += hLen;
        }

        if (r < hLen) {
            // Incomplete last block
            byte DK[] = new byte[dkLen];
            System.arraycopy(T, 0, DK, 0, dkLen);
            return DK;
        }
        return T;
    } 


    private static void F( byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex ) {
        final int hLen = prf.getMacLength();
        byte U_r[] = new byte[ hLen ];
        // U0 = S || INT (i);
        byte U_i[] = new byte[S.length + 4];
        System.arraycopy( S, 0, U_i, 0, S.length );
        INT( U_i, S.length, blockIndex );
        for( int i = 0; i < c; i++ ) {
            U_i = prf.doFinal( U_i );
            xor( U_r, U_i );
        }

        System.arraycopy( U_r, 0, dest, offset, hLen );
    }

    private static void xor( byte[] dest, byte[] src ) {
        for( int i = 0; i < dest.length; i++ ) {
            dest[i] ^= src[i];
        }
    }

    private static void INT( byte[] dest, int offset, int i ) {
        dest[offset + 0] = (byte) (i / (256 * 256 * 256));
        dest[offset + 1] = (byte) (i / (256 * 256));
        dest[offset + 2] = (byte) (i / (256));
        dest[offset + 3] = (byte) (i);
    } 

    // ctor
    private PBKDF2 () {}

}

I used test vectors found here PBKDF2-HMAC-SHA2 test vectors to verify the correctness of the implementation and it all checked out. I'm not sure why I couldn't the same results with an MD5 hashed password.

Parameters:

Salt: 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
Iterations Count: 1000
DKLen: 16 (128-bit derived key)

Using "foobar" as the plaintext password, the expected results are:

PWHash = MD5(PlaintextPassword) = 3858f62230ac3c915f300c664312c63f
PWKey = PBKDF2(PWHash, Salt, IterationsCount, DKLen) = 33C37758EFA6780C5E52FAB3B50F329C

What I get:

PWHash = 3858f62230ac3c915f300c664312c63f
PWKey = 0bd0c7d8339df2c66ce4b6e1e91ed3f1
Community
  • 1
  • 1
Android Noob
  • 3,271
  • 4
  • 34
  • 60
  • 1
    Why are you certain the "expected" results are correct? – President James K. Polk Feb 05 '12 at 14:06
  • I don't know if they are, but the rest of the authentication process + client-server communication in the project documentation uses this expected result in its examples to create crypto messages with AES-ECB to communicate with the server so I'd assume its correct. – Android Noob Feb 05 '12 at 15:39
  • Your method takes a byte array as the password parameter. How are you converting the text "foobar" into bytes? – rossum Feb 05 '12 at 15:54
  • To be honest, I'm being lazy because I don't want to read your code :) – President James K. Polk Feb 05 '12 at 15:54
  • @rossum MD5("foobar") = 3858f62230ac3c915f300c664312c63f, which are the bytes I feed into PBKDF2. - GregS: I'm not expecting you or anyone to :) But you can just cut and paste this solution, which I got from http://stackoverflow.com/questions/1012363/java-equivalent-of-cs-rfc2898derivedbytes into a Java IDE and run it in less than 5 minutes – Android Noob Feb 05 '12 at 16:03
  • Since you've already run test vectors through your code it is unlikely that is the problem. It is the other code I'm worried about, the one yours is expected to match. Maybe the other code treats the salt as a string of hex characters instead of a byte array, as just one example. – President James K. Polk Feb 05 '12 at 16:20
  • Hmm, that could be a possibility. I used java String.getBytes() call on the salt and also ran it through a hexStringToByteArray() method http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java and couldn't produce the same results. Our documentation states that the salt is "ASCII hex text of up to 256." In this case, the salt is exactly 256 bits (if put into a byte array, each element is a number from 0-31). Not sure what else it could be :( – Android Noob Feb 05 '12 at 17:40
  • This is wrong: `int l = Math.max( dkLen, hLen);`, but since that would give other problems, I guess that you have made some changes to your code before submitting your question. – Rasmus Faber Feb 06 '12 at 08:23
  • I get the same results as you (from a different implementation). Chances are that either your project documentation is wrong, you have read it wrong or that you are using the wrong input parameters. – Rasmus Faber Feb 06 '12 at 08:44
  • For the Math.max function, there was a CEIL function that was implemented according the the RFC spec. The author of the modified code I posted just simplified it by replacing it with Math.max. I think that part only influences the length of the DK. – Android Noob Feb 06 '12 at 15:35
  • Haha I figured it out on accident. The iteration count is supposed to be 4096. I don't know why the documentation says 1000. Looks like the author made a mistake. Thank you for all the help peeps. – Android Noob Feb 06 '12 at 15:55
  • 2
    You may notice that 4096 = 0x1000 – rossum Feb 06 '12 at 19:22
  • Interesting I didn't realize that. Regardless, my documentation never said anything about changing the iterations count from hex->integer. Plus, the RFC spec says the iterations count is supposed to be an integer. Thank you anyways. – Android Noob Feb 06 '12 at 19:55
  • 4
    @AndroidNoob: `0x1000` is just as much an integer as `4096` is. – caf Feb 07 '12 at 05:51

2 Answers2

3

The iterations count was supposed to 4096, not 1000.

Android Noob
  • 3,271
  • 4
  • 34
  • 60
0

The generation of int l seems wrong. You have specified the maximum between dkLen and hLen but the spec says l = CEIL (dkLen / hLen) with

CEIL (x) is the "ceiling" function, i.e. the smallest integer greater than, or equal to, x.

I think l would be more accurately defined as l = (int)Math.ceil( (double)dkLen / (double)hLen )

Douglas Held
  • 1,452
  • 11
  • 25