15

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)

weston
  • 54,145
  • 21
  • 145
  • 203
Arsenic
  • 343
  • 2
  • 13
  • Up please, I hope I could find a solution. – Arsenic Aug 27 '12 at 12:44
  • I found your question using a Google search. Actually, the Java implementation specifies the lower 8 bits of each character, so it is taking more information into account than the ASCII 7 bits. As it does not specify an encoding, this probably only confuses matters though. – Maarten Bodewes Jan 16 '13 at 21:12
  • Your question was flying below the radar, as it does not have any cryptography related tags. – Maarten Bodewes Jan 16 '13 at 21:26
  • You also haven't specified the `KeyFactory` you want to use. That there is no `PBEKeySpec` with a byte array for the password can be deducted by a simple Google search. I've assumed PBKDF2 in my answer. – Maarten Bodewes Jan 16 '13 at 21:30
  • I can see you haven't visited stackoverflow in a while, but could you please accept either one of my answers, or indicate why they don't suit your need? Then they don't keep showing up during reviews etc. Same goes for the other questions, which seem adequately answered. Thanks in advance. – Maarten Bodewes Jan 19 '13 at 15:06

4 Answers4

9

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);
    }
}
Yatendra
  • 1,310
  • 1
  • 18
  • 31
Soosta
  • 91
  • 1
  • 3
  • You made my day, thanks so much! I just modified it a little bit and made the method static so no instance of the class is required. 1k thanks! – Martin L. Dec 13 '16 at 19:45
  • you're very welcome, Martin. What goes around comes around, I've used stackoverflow many times :D – Soosta Dec 16 '16 at 13:38
  • Thanks a lot @Soosta, I was stucked for a while. You saved me ;-) – Cedekasme Jan 31 '17 at 11:15
  • 1
    @Soosta Do you have a link on where you found this? I've done some looking around and I've not found anything. I've run into the same situation where I have a byte[] for the password and not a char[]. I'd like to better understand the "why" behind the code. – Jeremy Whitlock Sep 15 '17 at 19:31
  • 1
    if only you had provided a call exampe with arguemnts. – mjs Dec 06 '20 at 10:41
  • This is the only solution that works without adding dependencies. Although it is noticeably slower than the standard char[] implementation at 100,000 iterations (a few seconds). I changed the INT function to use only bit shifts, removed all array allocations and `arraycopy`s in F, but this didn't help. – Caranown Feb 09 '21 at 13:43
3

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();
Community
  • 1
  • 1
Kelly
  • 1,096
  • 12
  • 22
1

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.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • I tried this without success. Trying to make a compatible implementation between .NET and Java and is being quite painful. I don't think that it uses only the lower 8 bits of the char, or expects some extra conversion from the byte[] (maybe encoding?) – Eric Lemes Jun 24 '15 at 14:53
  • @EricLemes Java basically uses the last 8 bits of the Unicode code points. That's however not compatible with UTF-8 but with the Windows-1252 / Western-Latin character sets. Maybe you should first convert back from UTF-8 encoding to `String` or `char[]` for this to work. – Maarten Bodewes Jun 24 '15 at 15:27
  • I think that is the problem. I'm using a byte array as input and during the UTF-8 conversions some bytes are dropped :-( They could use a byte[] as an input. Life will be much simpler... lol – Eric Lemes Jun 24 '15 at 15:30
  • @EricLemes Yeah, I think they used `char[]` because the password callback functions generally use that. So you would have to convert to `byte[]` for those. Android now allows UTF-8 I think, which is the coding hinted at in the PKCS#5 specs. This is generally more sensible, but harder to code securily. – Maarten Bodewes Jun 24 '15 at 15:33
  • I tried this and it sadly didn't work. It should! From my research, it seems like KeyFactory didn't work to spec until recent Java 8 versions. I'm stuck on Java 7 for a while so I posted an alternate solution, but yours should be correct in Java 8. – Kelly Nov 21 '16 at 16:49
  • I tried this every way I could think of (Java 13) and couldn't get it to work. Taking the binary byte data and simply interpreting two at a time as a char, only using the upper or lower end of a char, various charset encodings. Also assinging an int `byteArray[i] & 0xFF` to a char doesn't work. – Caranown Feb 09 '21 at 12:02
1

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);
    }
}
Alex L.
  • 89
  • 6