Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PBEKeySpec with byte array argument instead of ASCII

I would like to know if there is a way to use PBEKeySpec with a byte array argument.

Please find a link to the documentation below:

http://docs.oracle.com/javase/1.7/docs/api/javax/crypto/spec/PBEKeySpec.html)

like image 913
Arsenic Avatar asked Aug 24 '12 12:08

Arsenic


4 Answers

Here below is my solution: I got it googling around. Please consider I have to internally copy the password and the salt since they have another format when they come from the outside, but the result is the same. It seems it works and solves the problem of having a password as byte[] and not as char[] (it was driving me crazy) I hope it helps! Cheers, Soosta

public class Pbkdf2 {

    public Pbkdf2() {
    }

    public void GenerateKey(final byte[] masterPassword, int masterPasswordLen,
                            final byte[] salt, int saltLen,
                            int iterationCount, int requestedKeyLen,
                            byte[] generatedKey) {

        byte[] masterPasswordInternal = new byte[masterPasswordLen];
        System.arraycopy(masterPassword, 0, masterPasswordInternal, 0, masterPasswordLen);
        byte[] saltInternal = new byte[saltLen];
        System.arraycopy(salt, 0, saltInternal, 0, saltLen);


        SecretKeySpec keyspec = new SecretKeySpec(masterPasswordInternal, "HmacSHA1");
        Mac prf = null;
        try {
            prf = Mac.getInstance("HmacSHA1");
            prf.init(keyspec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }

        int hLen = prf.getMacLength();   // 20 for SHA1
        int l = Math.max(requestedKeyLen, hLen); //  1 for 128bit (16-byte) keys
        int r = requestedKeyLen - (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, saltInternal, iterationCount, i);
            ti_offset += hLen;
        }

        System.arraycopy(T, 0, generatedKey, 0, requestedKeyLen);
    }

    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);
    }
}
like image 117
Soosta Avatar answered Nov 13 '22 04:11

Soosta


I had to implement a two-phase pbkdf2 derivation (so the second pbkdf2 had bytes from the first as input). I ended up using BouncyCastle because I just couldn't get the byte array to char array gymnastics to work. Credit to Pasi from this other question: Reliable implementation of PBKDF2-HMAC-SHA256 for JAVA

import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.GeneralDigest;
import org.bouncycastle.crypto.params.KeyParameter;

GeneraDigest algorithm = new SHA256Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(algorithm);
gen.init(passwordBytes, salt, iterations);
byte[] dk = ((KeyParameter) gen.generateDerivedParameters(256)).getKey();
like image 44
Kelly Avatar answered Nov 13 '22 04:11

Kelly


As the Java PKCS#5 KeyFactory has been specified to only use the lower 8 bits of the characters in the PBEKeySpec, you should be able to convert your byte array into a (16 bit) character array without issue. Just copy the value of each byte into the character array and you should be set.

Just to be sure, I would perform charArray[i] = byteArray[i] & 0xFF as assignment statement, otherwise you would get very high valued characters.

It's an ugly workaround, but I don't see any reason why it should not work.


Note that the above assumes Latin / Windows 1252 compatible encoding for values 0x80 and over. If you allow code points of 0x80 to 0xFF then you cannot use UTF-8 (or UTF-16 of course) as encoding.

like image 1
Maarten Bodewes Avatar answered Nov 13 '22 03:11

Maarten Bodewes


I was able to do this using a 3rd party library and extending one of their classes.

Here is the RFC 2898 implementation library that I used: http://www.rtner.de/software/PBKDF2.html

My code:

import de.rtner.security.auth.spi.PBKDF2Engine;
import de.rtner.security.auth.spi.PBKDF2Parameters;

public class PBKDF2Utils {
    
    private static class PBKDF2EngineWithBinaryPassword extends PBKDF2Engine {
        
        private PBKDF2EngineWithBinaryPassword(PBKDF2Parameters parameters) {
            super(parameters);
        }

        public byte[] deriveKey(byte[] inputPassword, int dkLen) {
            this.assertPRF(inputPassword);
            return this.PBKDF2(prf, parameters.getSalt(), parameters.getIterationCount(), dkLen);
        }
    }
    
    public static byte[] deriveKey(
            byte[] password, 
            byte[] salt,
            int iterationCount, 
            int dkLen) {
        
        PBKDF2Parameters parameters = new PBKDF2Parameters("HmacSHA1", null, salt, iterationCount);
        
        PBKDF2EngineWithBinaryPassword engine = new PBKDF2EngineWithBinaryPassword(parameters);
        
        return engine.deriveKey(password, dkLen);
    }
}
like image 1
Alex L. Avatar answered Nov 13 '22 05:11

Alex L.