Reputation: 450
There is a server which encrypt password on C# (method encryptCSharp), android app receives encrypted password, salt, passPhrase and initVector.
I have to decrypt that password in Java.
Server-guy sent me an encrypt method in C# and I need to created encryptJava and decryptJava methods which works the same in Java as in C#.
To create PasswordDeriveBytes
which absent in Java I use an example from here Encryption Diff Between Java and C#
So, my question is, what's going wrong with my Java methods? Both didn't work
Update: I made some changes in snippets and now all works!!
I invoke that methods:
String encryptedText = encryptJava("12345", "100", "@.erf.net34", "@gugnet@gugnet77");//it works!!
String decryptedText = decryptJava(encryptedText, "100", "@.erf.net34", "@gugnet@gugnet77");//it doesn't work!!
Here are Java methods that I used and C# method from server.
C# (which I can't change)
public static String encryptCSharp(String plainText, String saltValue, String passPhrase, String initVector) {
String hashAlgorithm = "SHA1";
int passwordIterations = 1;
int keySize = 256;
// Convert strings into byte arrays.
// Let us assume that strings only contain ASCII codes.
// If strings include Unicode characters, use Unicode, UTF7, or UTF8
// encoding.
byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
// Convert our plaintext into a byte array.
// Let us assume that plaintext contains UTF8-encoded characters.
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
// First, we must create a password, from which the key will be derived.
// This password will be generated from the specified passphrase and
// salt value. The password will be created using the specified hash
// algorithm. Password creation can be done in several iterations.
PasswordDeriveBytes password = new PasswordDeriveBytes(
passPhrase,
saltValueBytes,
hashAlgorithm,
passwordIterations);
// Use the password to generate pseudo-random bytes for the encryption
// key. Specify the size of the key in bytes (instead of bits).
byte[] keyBytes = password.GetBytes(keySize / 8);
// Create uninitialized Rijndael encryption object.
RijndaelManaged symmetricKey = new RijndaelManaged();
// It is reasonable to set encryption mode to Cipher Block Chaining
// (CBC). Use default options for other symmetric key parameters.
symmetricKey.Mode = CipherMode.CBC;
// Generate encryptor from the existing key bytes and initialization
// vector. Key size will be defined based on the number of the key
// bytes.
ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
keyBytes,
initVectorBytes);
// Define memory stream which will be used to hold encrypted data.
MemoryStream memoryStream = new MemoryStream();
// Define cryptographic stream (always use Write mode for encryption).
CryptoStream cryptoStream = new CryptoStream(memoryStream,
encryptor,
CryptoStreamMode.Write);
// Start encrypting.
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
// Finish encrypting.
cryptoStream.FlushFinalBlock();
// Convert our encrypted data from a memory stream into a byte array.
byte[] cipherTextBytes = memoryStream.ToArray();
// Close both streams.
memoryStream.Close();
cryptoStream.Close();
// Convert encrypted data into a base64-encoded string.
String cipherText = Convert.ToBase64String(cipherTextBytes);
// Return encrypted string.
return cipherText;
}
Java to encrypt message, it works and I got the same result as method in C#
private String encryptJava(String plainText, String saltValue, String passPhrase, String initVector) {//working!!!
String result = "";
byte[] initVectorBytes = initVector.getBytes(US_ASCII);
byte[] saltValueBytes = saltValue.getBytes(US_ASCII);
byte[] plainTextBytes = plainText.getBytes(UTF_8);
Cipher cipher;
try {
final com.gmail.example.PasswordDeriveBytes password = new com.gmail.example.PasswordDeriveBytes(passPhrase, saltValueBytes);
final byte[] keyBytes = password.getBytes(256 / Byte.SIZE);
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
try {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(initVectorBytes));
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
final byte[] ct = cipher.doFinal(plainTextBytes);
result = Base64.encodeToString(ct, Base64.DEFAULT);//**added this line!**
//result = new String(ct, "US-ASCII");**-- deleted this line!**
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
return result;
}
Java method that emulate the same method in C#
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 = 1;//**changed from 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;
}
}
and finally Java method that doesn't work and try to understand what I do wrong
private String decryptJava(String encryptedText, String saltValue, String passPhrase, String initVector) {
String result = "";
byte[] initVectorBytes = initVector.getBytes(US_ASCII);
byte[] saltValueBytes = saltValue.getBytes(US_ASCII);
byte[] encryptedTexttBytes = Base64.decode(encryptedText, Base64.DEFAULT);
Cipher cipher;
try {
final PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes);
final byte[] keyBytes = password.getBytes(256 / Byte.SIZE);
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
try {
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(initVectorBytes));
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
final byte[] ct = cipher.doFinal(encryptedTexttBytes);
//result = Base64.encodeToString(ct, Base64.DEFAULT); - **deleted this line**
try {
result = new String(ct, "US-ASCII");//** added this line**
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
return result;
}
Upvotes: 1
Views: 2047
Reputation: 450
Finally I found a solution for encoding and deconding method: (so that to not increase the amount of code here, I made changes in snippets above)
in encryptJava
method I changed one line,
in PasswordDeriveBytes
I changed iterations from 100 to 1
in decryptJava
method I added one line and deleted one line.
Upvotes: 1
Reputation: 93968
With PasswordDeriveBytes
never ever ever ask for more bytes than the underlying hash function. This function implements PBKDF1 which cannot output more than that number of bits (160 for SHA-1 in your example).
The Microsoft implementation allows more output, but the implementation is broken to the extreme (it may even repeat output!). Use Rfc2898DeriveBytes
instead which implements PBKDF2, also available in Java. Use a larger hash, PBKDF2 can generate more than the output of the hash as bytes, but only at the cost of security.
Upvotes: 1