Java equivalent of C#'s Rfc2898DerivedBytes

2019-01-17 19:12发布

问题:

I was wondering if anyone have tried to do an equivalent of

Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(secret, saltValueBytes);
byte[] secretKey = key.GetBytes(16);

in Java. Where secret is a string(password), and saltValueBytes is, well, a salt in byte array.

I've tried stuff, but can't seem to wrap my head around it.

回答1:

I found this implementation by means of a Google search but I have never used it.

A free Java implementation of RFC 2898 / PKCS#5 PBKDF2

There seems to be no small and freely available Java implementation of RFC 2898 / PKCS#5 available. Small as in only a few source files, with trivial compile and no dependencies, free as in LGPL.

Given the availability of HMacSHA1 in the standard SUN JCE cryptographic provider, such an implementation is quite simple and can be derived from the RFC description quite literally. My code is a clean-room implementation with only the RFC as its basis.



回答2:

I know this is late to the game, but Java 6 and up does have a built-in PBKDF2 implementation.

int dkLen = 64;
int rounds = 1000;
PBEKeySpec keySpec = new PBEKeySpec("Some password".toCharArray(), "SomeSalt".getBytes(), rounds, dkLen * 8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] out = factory.generateSecret(keySpec).getEncoded();

The list of Java 6 Security Enhancements claims comparability with PKCS#5, and through my own (cursory) testing it does appear to produce correct PBKDF2 keys.



回答3:

This one works for me. I still am in disbelief that a standard implementation of an RFC2898-compliant PBKDF2 is not present in the JRE. I think I must be looking in the wrong places. The name confusion (RFC2898 PKCS5 PBKDF2) doesn't help.

// PBKDF2.java
// ------------------------------------------------------------------
//
// RFC2898 PBKDF2 in Java.  The RFC2898 defines a standard algorithm for
// deriving key bytes from a text password.  This is also called 
// "PBKDF2", for Password-based key derivation function #2.
//
// There's no RFC2898-compliant PBKDF2 function in the JRE, as far as I
// know, but it is available in many J2EE runtimes, including those from
// JBoss, IBM, and Oracle.
//
// It's fairly simple to implement, so here it is. 
// 
// Author: Admin
// built on host: DINOCH-2
// Created Sun Aug 09 01:06:57 2009
//
// last saved: 
// Time-stamp: <2009-August-09 11:11:47>
// ------------------------------------------------------------------
//
// code from Matthias Gartner
//
// ------------------------------------------------------------------

package cheeso.examples;


import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;


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, "HmacSHA1" );
        Mac prf = Mac.getInstance( "HmacSHA1" );
        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.
        //
        // hLen denotes the length in octets of the pseudorandom function output
        // dklen the length in octets (bytes) of the derived key. 

        int hLen = prf.getMacLength();   // 20 for SHA1
        int l = Math.ceil( 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 () {}
}


回答4:

Slightly improved Cheeso's code for working with HMacSHA256 or HMacSHA512 by adding overloaded deriveKey(). With this change, the code was run with PKDF2-HMAC-SHA512 test vectors from
PHP Crypt lib which resulted in 6 failures out of 100 test cases.

// PBKDF2.java
// ------------------------------------------------------------------
//
// RFC2898 PBKDF2 in Java.  The RFC2898 defines a standard algorithm for
// deriving key bytes from a text password.  This is also called 
// "PBKDF2", for Password-based key derivation function #2.
//
// There's no RFC2898-compliant PBKDF2 function in the JRE, as far as I
// know, but it is available in many J2EE runtimes, including those from
// JBoss, IBM, and Oracle.
//
// It's fairly simple to implement, so here it is. 
// 
// Author: Admin
// built on host: DINOCH-2
// Created Sun Aug 09 01:06:57 2009
//
// last saved: 
// Time-stamp: <2009-August-09 11:11:47>
// ------------------------------------------------------------------
//
// code from Matthias Gartner
//
// ------------------------------------------------------------------

package cheeso.examples;


import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;


public class PBKDF2
{
    public static byte[] deriveKey(byte[] password,
                                   byte[] salt,
                                   int iterationCount,
                                   int dkLen)
            throws NoSuchAlgorithmException,
            InvalidKeyException
    {
        return deriveKey("HmacSHA1", password, salt, iterationCount, dkLen);
    }

    public static byte[] deriveKey(String hmacAlgo,
                                   byte[] password,
                                   byte[] salt,
                                   int iterationCount,
                                   int dkLen)
            throws NoSuchAlgorithmException,
            InvalidKeyException
    {
        SecretKeySpec keyspec = new SecretKeySpec(password, hmacAlgo);
        Mac prf = Mac.getInstance(hmacAlgo);
        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, 32 for 256)

        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 () {}
}