Initial Bytes Incorrect After Java Aes/Cbc Decryption

Initial bytes incorrect after Java AES/CBC decryption

Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.

Hope this will be useful to you all:
To compile you need additional Apache Commons Codec jar, which is available here:
http://commons.apache.org/proper/commons-codec/download_codec.cgi

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

byte[] encrypted = cipher.doFinal(value.getBytes());
System.out.println("encrypted string: "
+ Base64.encodeBase64String(encrypted));

return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}

return null;
}

public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}

return null;
}

public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV

System.out.println(decrypt(key, initVector,
encrypt(key, initVector, "Hello World")));
}
}

First 8 bytes is truncating in AES decryption in java where encryption is done in c#

As I don't like to mix answers I'm writing a second answer with the final solution for your question.

Thanks for providing the C#-code as it answered 2 questions - what is the encoding of the plaintext and what data is included in the ciphertext.

Using this line on C# shows us that the encoding is UNICODE (=UTF 16) as I argued in my first answer:

byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);

To get identical results in Java you need to decode any decryption result with

s = new String(original, StandardCharsets.UTF_16LE);

The second question - what is included in the ciphertext is answered very simple - just the ciphertext itself (no IV). The neccessary IV is derived by the

Rfc2898DeriveBytes...
encryptor.Key = rfcdb.GetBytes(32);
encryptor.IV = rfcdb.GetBytes(16);

Putting both information together I can decrypt the ciphertext with the given key as follows:

First 8 bytes is truncating in AES decryption in java where encryption is done in c#
cipherByte length: 48 data: 87e549e68af6c8115061ac0d597113d759de914cd6618556934fced9f1f1a0b366eb4977777a90a363498c6af7fb364f
keyDerived length: 32 data: e35b88684a86fe06ae80eab429b2c9c242ab64f225649aa484ced8f53e5de146
ivB length: 16 data: 19b9e11cb1e6c6aa69060fbdfe10484c
original length: 40 data: 4400750070006c006900630061007400650020005200650071007500650073007400200049004400
Duplicate Request ID
Duplicate Request ID

Here is the complete code:

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;

public class mainSo2 {

public static void main(String[] args) {
System.out.println("First 8 bytes is truncating in AES decryption in java where encryption is done in c#");
String cipherstring = "h+VJ5or2yBFQYawNWXET11nekUzWYYVWk0/O2fHxoLNm60l3d3qQo2NJjGr3+zZP";
byte[] cipherByte = Base64.getDecoder().decode(cipherstring);
System.out.println("cipherByte length: " + cipherByte.length + " data: " + bytesToHex(cipherByte));
String cum006333 = decrypt(cipherstring, "KEY@CRISIL123");
System.out.println(cum006333);
}

public static String decrypt(String cipherText, String passphrase) {
String s = null;
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] bytes = Base64.getDecoder().decode(cipherText);
int keySize = 256;
int iterCount = 1000;
int ivLength = 16;
byte[] saltByte = new byte[]{0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76};
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), saltByte, iterCount, (keySize + (8 * ivLength))); // 32 + 16
byte[] rawKey = factory.generateSecret(spec).getEncoded(); // 48 bytes
byte[] keyDerived = Arrays.copyOf(rawKey, (keySize / 8)); // first 32 bytes
byte[] ivB = Arrays.copyOfRange(rawKey, (keySize / 8), rawKey.length); // last 16 bytes
System.out.println("keyDerived length: " + keyDerived.length + " data: " + bytesToHex(keyDerived));
System.out.println("ivB length: " + ivB.length + " data: " + bytesToHex(ivB));
SecretKey key = new SecretKeySpec(keyDerived, "AES");
IvParameterSpec iv = new IvParameterSpec(ivB);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
//byte[] original = cipher.doFinal(data);
byte[] original = cipher.doFinal(bytes);
System.out.println("original length: " + original.length + " data: " + bytesToHex(original));
s = new String(original, StandardCharsets.UTF_16LE);
System.out.println(s);
} catch (Exception ex) {
ex.printStackTrace();
}
return s;
}

private static String bytesToHex(byte[] bytes) {
StringBuffer result = new StringBuffer();
for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
}

PHP AES-256-CBC encrypted data is different from JAVA AES/CBC/PKCS5PADDING

There are the following issues:

  • In the PHP code, the key is currently returned hex encoded. Instead, it must be returned as bytes string. To do this, the third parameter in hash() must be switched from false to true.
  • In the Java code a 192 bits key is used, i.e. AES-192. Accordingly, in the PHP code "AES-192-CBC" must be applied (and not "AES-256-CBC").
  • The utf8_encode() call in the PHP code is to be removed, as this corrupts the key.

With these changes, both codes provide the same ciphertext.

Security:

Using SHA256 as key derivation is insecure. Instead apply a dedicated algorithm like Argon2 or PBKDF2. Also, using the key (or a part of it) as IV is insecure as it results in the reuse of key/IV pairs. Instead, a randomly generated IV should be applied for each encryption.

AES Decryption when writing to a file using base64 encoding

In the Encryption part, you used AES/CBC/PKCS5Padding that is CBC mode of operation with PKCS#5 - actually, it is PKCS#7 - the there is an IV is missing. The CBC mode requires and IV and that should be at least random. That can be generated with

SecureRandom randomSecureRandom = new SecureRandom();
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

In the decryption part, you used AES/ECB/PKCS5Padding. Normally both must match. Make both either ECB ( please don't) or CBC with the IV.

The IV in generally, appended to the beginning of the ciphertext. During decryption resolved it and use it.

Also, make the base64 decoding inside of the decryption. This will make your mode more robust.

Final note: if there is no specific reason, prefer AES-GCM which can provide not only confidentiality but also integrity and authentication. If you need to stick CBC you need additionally use HMAC to achieve integrity and authentication.

And for a full code example; See the Maartens answer

AES/CBC/NoPadding encryption and decryption

@RequiresApi(api = Build.VERSION_CODES.O)
private String encrypt(String message, String key) throws Exception{
byte[] encval=null;
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
byte[] ivBytes = new byte[16];
cipher.init(Cipher.ENCRYPT_MODE,skey,new IvParameterSpec(ivBytes)); //iv here
encval=cipher.doFinal(message.getBytes());
String encryptedValue = Base64.getEncoder().encodeToString(encval);
return encryptedValue;

}

you have to provide iv in encrypt too



Related Topics



Leave a reply



Submit