PBEKeySpec with byte array argument instead of ASCII
Asked Answered
A

4

16

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)

Adrieneadrienne answered 24/8, 2012 at 12:59 Comment(5)
Up please, I hope I could find a solution.Adrieneadrienne
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.Tevet
Your question was flying below the radar, as it does not have any cryptography related tags.Tevet
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.Tevet
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.Tevet
P
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);
    }
}
Partisan answered 23/11, 2016 at 7:1 Comment(6)
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!Haga
you're very welcome, Martin. What goes around comes around, I've used stackoverflow many times :DPartisan
Thanks a lot @Soosta, I was stucked for a while. You saved me ;-)Aelber
@Partisan 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.Sunwise
if only you had provided a call exampe with arguemnts.Phoenician
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 arraycopys in F, but this didn't help.Terriss
C
4

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();
Cavitation answered 21/11, 2016 at 16:43 Comment(0)
A
2

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);
    }
}
Anticosti answered 15/1, 2021 at 22:17 Comment(0)
T
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.

Tevet answered 16/1, 2013 at 21:21 Comment(6)
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?)Halide
@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.Tevet
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... lolHalide
@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.Tevet
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.Cavitation
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.Terriss

© 2022 - 2024 — McMap. All rights reserved.