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)
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)
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);
}
}
arraycopy
s in F, but this didn't help. –
Terriss 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();
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);
}
}
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.
String
or char[]
for this to work. –
Tevet 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 byteArray[i] & 0xFF
to a char doesn't work. –
Terriss © 2022 - 2024 — McMap. All rights reserved.
KeyFactory
you want to use. That there is noPBEKeySpec
with a byte array for the password can be deducted by a simple Google search. I've assumed PBKDF2 in my answer. – Tevet