Hi I am trying to figure out how to replicate the encryption of text done in C# but in Java. the part of the code that is still puzzling me and cant seem to find the answer to is this in C#:
PasswordDeriveBytes myPass = new PasswordDeriveBytes(String Password, byte[] Salt);
Trp.Key = myPass.GetBytes(24);
Trp.IV = myPass.GetBytes(8);
Basically what would be the equivalent of this code in Java? UPDATE: Using the provided code for PasswordDeriveBytes (the second snippet) i was able to replicate the C# code perfectly. Thank you Maarten Bodewes.
BASE64Encoder base64 = new BASE64Encoder();
PasswordDeriveBytes i_Pass = new PasswordDeriveBytes(passWord, saltWordAsBytes);
byte[] keyBytes = i_Pass.getBytes(24);
byte[] ivBytes = i_Pass.getBytes(8);
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(keyBytes, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(ivBytes);
c3des.init (Cipher.ENCRYPT_MODE, myKey, ivspec);
encrytpedTextAsByte = c3des.doFinal(plainTextAsBytes);
encryptedText = base64.encode(encrytpedTextAsByte);
but can't seem to get it to work across platform. basically the decoding code is set (i cant change in C# 3.5) and i am trying to encode in java so that the C# code can decode it.
Any help would be greatly appreciated.
The problem is that PasswordDeriveBytes
is only defined for the first 20 bytes - in that case it is PBKDF1 (not 2, as you are currently using in your Java code). Calling getBytes
multiple times may also change the result. The algorithm for one or more calls to getBytes
or for more than 20 bytes is Microsoft proprietary and doesn't seem to be described anywhere. In Mono it's even described as a non-fix as it may not be secure.
I would strongly suggest to use RFC2898DeriveBytes that does implement PBKDF2. Beware to only use it for ASCII input or it may not be compatible with the Java implementation.
The only other option is to figure out the proprietary extension of Microsoft PasswordDeriveBytes
to PBKDF1 (which only defines output up to the hash size of 20 bytes). I've reimplemented the version of Mono below.
Repeated request to Microsoft to update the API description of this function did not produce any results. You may want to read this bug report if your results differ.
This is the proprietary Microsoft extension. Basically it first calculates PBKDF-1 up to but not including the last hash iteration, call this HX. For the first 20 bytes it simply performs another hash, so it is compliant to PBKDF1. The next hashes are over the ASCII representation of a counter starting at 1 (so this is first converted to "1"
, then to 0x31
), followed by the bytes of HX.
What follows is a minimalistic, rather direct conversion from the Mono code:
public class PasswordDeriveBytes {
private final MessageDigest hash;
private final byte[] initial;
private final int iterations;
private byte[] output;
private int hashnumber = 0;
private int position = 0;
public PasswordDeriveBytes(String password, byte[] salt) {
try {
this.hash = MessageDigest.getInstance("SHA-1");
this.initial = new byte[hash.getDigestLength()];
this.hash.update(password.getBytes(UTF_8));
this.hash.update(salt);
this.hash.digest(this.initial, 0, this.initial.length);
this.iterations = 100;
} catch (NoSuchAlgorithmException | DigestException e) {
throw new IllegalStateException(e);
}
}
public byte[] getBytes(int cb) {
if (cb < 1)
throw new IndexOutOfBoundsException("cb");
byte[] result = new byte[cb];
int cpos = 0;
// the initial hash (in reset) + at least one iteration
int iter = Math.max(1, iterations - 1);
// start with the PKCS5 key
if (output == null) {
// calculate the PKCS5 key
output = initial;
// generate new key material
for (int i = 0; i < iter - 1; i++)
output = hash.digest(output);
}
while (cpos < cb) {
byte[] output2 = null;
if (hashnumber == 0) {
// last iteration on output
output2 = hash.digest(output);
} else if (hashnumber < 1000) {
String n = String.valueOf(hashnumber);
output2 = new byte[output.length + n.length()];
for (int j = 0; j < n.length(); j++)
output2[j] = (byte) (n.charAt(j));
System.arraycopy(output, 0, output2, n.length(), output.length);
// don't update output
output2 = hash.digest(output2);
} else {
throw new SecurityException();
}
int rem = output2.length - position;
int l = Math.min(cb - cpos, rem);
System.arraycopy(output2, position, result, cpos, l);
cpos += l;
position += l;
while (position >= output2.length) {
position -= output2.length;
hashnumber++;
}
}
return result;
}
}
Or, a bit more optimized and readable, leaving just the output buffer and position to be changed in between calls:
public class PasswordDeriveBytes {
private final MessageDigest hash;
private final byte[] firstToLastDigest;
private final byte[] outputBuffer;
private int position = 0;
public PasswordDeriveBytes(String password, byte[] salt) {
try {
this.hash = MessageDigest.getInstance("SHA-1");
this.hash.update(password.getBytes(UTF_8));
this.hash.update(salt);
this.firstToLastDigest = this.hash.digest();
final int iterations = 100;
for (int i = 1; i < iterations - 1; i++) {
hash.update(firstToLastDigest);
hash.digest(firstToLastDigest, 0, firstToLastDigest.length);
}
this.outputBuffer = hash.digest(firstToLastDigest);
} catch (NoSuchAlgorithmException | DigestException e) {
throw new IllegalStateException("SHA-1 digest should always be available", e);
}
}
public byte[] getBytes(int requested) {
if (requested < 1) {
throw new IllegalArgumentException(
"You should at least request 1 byte");
}
byte[] result = new byte[requested];
int generated = 0;
try {
while (generated < requested) {
final int outputOffset = position % outputBuffer.length;
if (outputOffset == 0 && position != 0) {
final String counter = String.valueOf(position / outputBuffer.length);
hash.update(counter.getBytes(US_ASCII));
hash.update(firstToLastDigest);
hash.digest(outputBuffer, 0, outputBuffer.length);
}
final int left = outputBuffer.length - outputOffset;
final int required = requested - generated;
final int copy = Math.min(left, required);
System.arraycopy(outputBuffer, outputOffset, result, generated, copy);
generated += copy;
position += copy;
}
} catch (final DigestException e) {
throw new IllegalStateException(e);
}
return result;
}
}
It's actually not all that bad security-wise as the bytes are separated from each other by the digest. So the key stretching is relatively OK. Note that there were Microsoft PasswordDeriveBytes
implementations out there that contained a bug and repeated bytes (see bug report above). This is not reproduced here.
Usage:
private static final String PASSWORD = "46dkaKLKKJLjdkdk;akdjafj";
private static final byte[] SALT = { 0x26, 0x19, (byte) 0x81, 0x4E,
(byte) 0xA0, 0x6D, (byte) 0x95, 0x34 };
public static void main(String[] args) throws Exception {
final Cipher desEDE = Cipher.getInstance("DESede/CBC/PKCS5Padding");
final PasswordDeriveBytes myPass = new PasswordDeriveBytes(PASSWORD, SALT);
final SecretKeyFactory kf = SecretKeyFactory.getInstance("DESede");
final byte[] key = myPass.getBytes(192 / Byte.SIZE);
final SecretKey desEDEKey = kf.generateSecret(new DESedeKeySpec(key));
final byte[] iv = myPass.getBytes(desEDE.getBlockSize());
desEDE.init(Cipher.ENCRYPT_MODE, desEDEKey, new IvParameterSpec(iv));
final byte[] ct = desEDE.doFinal("owlstead".getBytes(US_ASCII));
}
Side notes about the Java implementation:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With