AES/CBC Decryption not working as expected - java

I'm having some issued decrypting data using AES/CBC/PKCS5Padding in Java.
I am encrypting two values A and B and then data from a file. The encrypted values are written in file in the described sequence.
When decrypting the bytes for the respective pieces are properly located (confirmed via debugging) and the inputs to the decrypting functions are correct, no padding issues there.
Encryption code:
byte[] iv = {..........};
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, fileKey, ivParameterSpec);
byte[] encryptedA = cipher.update(A);
byte[] encryptedB = cipher.update(B);
while( true){
if( blocks > 1 ) {
encrypted = cipher.update(data);
}
else {
encrypted = cipher.doFinal(data);
}
blocks--;
//write bytes to file
}
When encrypting I can see that the vector inside the Cipher is updated after every update() as expected (the last ciphertext is the vector for subsequent updates. for example encryptedA is the vector of the cipher at the time I call update(B)
Decryption code
Cipher cipherB = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
cipherB.init(Cipher.DECRYPT_MODE, fileKey, ivParameterSpecB);
byte[] decryptedA = cipherB.update(encryptedA);
byte[] decryptedB = cipherB.update(encryptedB);
while( true){
if(blocks > 1 ) {
decrypted = cipherB.update(encryptedBytes);
}
else {
decrypted = cipherB.doFinal(encryptedBytes);
}
blocks--;
//write bytes to file
}
What's happening at this point very odd.
The first call to cipherB.update(encryptedA) does nothing at all. It returns an empty array and doesn't update the vector inside the cipher.
The second call to cipherB.update(encryptedB) returns the value I was expecting from the previous call ( cipherB.update(encryptedA) which is the original value: A) and sets the vector to the value of encryptedA
Can you spot anything wrong in my approach? Are there any known issues in AES/CBC/PKCS5Padding when using the default SunJCE provider?
Update:
After reading some of the comments, let me add some extra clarification
the conditions are around the blocks used for encrypting the payload. the first condition is simple while(true) and the second one is if(blockCount > 1). there's a block counter that's been decreased in every loop. Code updated
If A and B are omitted from the encryption/decryption, the file data is properly been decrypted
I tried decrypting the direct outcome of the encryption
for example:
cipherB.update(cipher.update(A))
but I'm still getting the same empty array instead of A
I cannot rely on running updates twice, after getting back B by running cipherB.update(encryptedB) something is going wrong and the file data decryption is affected by the vector in the cipher.
The data I get back is something like
(12 random bytes)Lorem Ipsum etc

The plaintext and ciphertext chunks do not correspond to each other 1 to 1. You need to capture the full output in a byte[] and unpack it yourself.

The AES/CBC/PKCS5Padding mode works on blocks, so the update will return you only "filled" blocks and doFinal will return you the rest. AES is using 128 bit block, so the update method returns only multiples of 16 bytes. As well there is a last block with padding. So your assumption cipherB.update(cipher.update(A)) doesn't work in this case.
I'm not really following what are you trying to achieve by the condition if(blocks > 1 )
You could use following code to process cipher blocks (simplified version):
byte[] decrypted = null;
byte[] buffer = new byte[BUFFER_SIZE];
InputStream in = ..;
for (int bytesRead=in.read(buffer); bytesRead>=0; bytesRead=in.read(buffer)) {
decrypted = cipher.update(buffer, 0, bytesRead);
// process the chunk
}
decrypted = cipher.doFinal();
// process the chunk
this way it doesn't matter if you process a single block or not.
There are as well "stream ciphers" or modes when the update method directly returns encrypted or decrypted chunk regardless of the input size, such as AES/CTR mode or Salsa20 cipher

Related

CryptoJS AES encryption and Java AES decryption

I'm only asking this because I have read many posts for 2 days now about crypto AES encryption, and just when I thought I was getting it, I realized I wasn't getting it at all.
This post is the closest one to my issue, I have exactly the same problem but it is unanswered:
CryptoJS AES encryption and JAVA AES decryption value mismatch
I have tried doing it in many ways but I haven't gotten it right.
First Off
I'm getting the already encrypted string (I only got the code to see how they were doing it), so modifying the encryption way is not an option. That's why all the similar questions aren't that useful to me.
Second
I do have access to the secret key and I can modify it (so adjusting length is an option if neccessary).
The encryption is done on CryptoJS and they send the encrypted string as a GET parameter.
GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
return new Promise((resolve, reject) => {
const currentDateInMilliseconds = new Date().getTime();
const secret = tokenSecret.secret;
var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
encrypted = encrypted.toString();
self.urlParams = {
token: encrypted,
time: currentDateInMilliseconds
};
resolve();
});
};
I can easily decrypt this on javascript using CryptoJS with:
var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
But I don't want to do this on Javascript, for security reasons, so I'm trying to decrypt this on Java:
String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);
Before I had any idea of what I was doing, I tried base64 decoding, adding some IV and a lot of stuff I read, of course none of it worked.
But after I started to understand, kinda, what I was doing, I wrote that simple script above, and got me the same error on the post: Invalid AES key length
I don't know where to go from here. After reading a lot about this, the solution seems to be hashing or padding, but I have no control on the encryption method, so I can't really hash the secret or pad it.
But as I said, I can change the secret key so it can match some specific length, and I have tried changing it, but as I'm shooting in the dark here, I don't really know if this is the solution.
So, my question basically is, If I got the encrypted string (in javascript like the first script) and the secret key, is there a way to decrypt it (in Java)? If so, how to do it?
Disclaimer: Do not use encryption unless you understand encryption concepts including chaining mode, key derivation functions, IV and block size. And don't roll your own security scheme but stick to an established one. Just throwing in encryption algorithms doesn't mean an application has become any more secure.
CryptoJS implements the same key derivation function as OpenSSL and the same format to put the IV into the encrypted data. So all Java code that deals with OpenSSL encoded data applies.
Given the following Javascript code:
var text = "The quick brown fox jumps over the lazy dog. 👻 👻";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);
We get the cipher text:
U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
On the Java side, we have
String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
The result is:
The quick brown fox jumps over the lazy dog. 👻 👻
That's the text we started with. And emojis, accents and umlauts work as well.
GenerateKeyAndIV is a helper function that reimplements OpenSSL's key derivation function EVP_BytesToKey (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
/**
* Generates a key and an initialization vector (IV) with the given salt and password.
* <p>
* This method is equivalent to OpenSSL's EVP_BytesToKey function
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
* </p>
* #param keyLength the length of the generated key (in bytes)
* #param ivLength the length of the generated IV (in bytes)
* #param iterations the number of digestion rounds
* #param salt the salt data (8 bytes of data or <code>null</code>)
* #param password the password data (optional)
* #param md the message digest algorithm to use
* #return an two-element array with the generated key and IV
*/
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
Note that you have to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy. Otherwise, AES with key size of 256 won't work and throw an exception:
java.security.InvalidKeyException: Illegal key size
Update
I have replaced Ola Bini's Java code of EVP_BytesToKey, which I used in the first version of my answer, with a more idiomatic and easier to understand Java code (see above).
Also see How to decrypt file in Java encrypted with openssl command using AES?.
When encrypting on one system and decrypting on another you are at the mercy of system defaults. If any system defaults do not match (and they often don't) then your decryption will fail.
Everything has to be byte for byte the same on both sides. Effectively that means specifying everything on both sides rather than relying on defaults. You can only use defaults if you are using the same system at both ends. Even then, it is better to specify exactly.
Key, IV, encryption mode, padding and string to bytes conversion all need to be the same at both ends. It is especially worth checking that the key bytes are the same. If you are using a Key Derivation Function (KDF) to generate your key, then all the parameters for that need to be the same, and hence specified exactly.
Your "Invalid AES key length" may well indicate a problem with generating your key. You use getBytes(). That is probably an error. You need to specify what sort of bytes you are getting: ANSI, UTF-8, EBCDIC, whatever. The default assumption for the string to byte conversion is the likely cause of this problem. Specify the conversion to be used explicitly at both ends. That way you can be sure that they match.
Crypto is designed to fail if the parameters do not match exactly for encryption and decryption. For example, even a one bit difference in the key will cause it to fail.

AES128 Decryption :javax.crypto.badpaddingexception pad block corrupted

I try to decrypt an encrypted data that I receive from a web service.
The encryption is done using AES 128.
I use the following code to decrypt the data:
public static String decrypt(String strToDecrypt)
{
try
{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); //AES/CBC/PKCS7Padding
SecretKeySpec secretKey = new SecretKeySpec(AppConstants.AESEncryptionKey.getBytes("UTF8"), "AES");
int blockSize = cipher.getBlockSize();
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[blockSize])); //new IvParameterSpec(new byte[16])
byte decBytes[] = cipher.doFinal(Base64.decode(strToDecrypt, 0));
// byte decBytes[] = cipher.doFinal(Base64.decodeBase64(strToDecrypt));
String decStr = new String(decBytes);
System.out.println("After decryption :" + decStr);
return decStr;
}
catch (Exception e)
{
System.out.println("Exception in decryption : " + e.getMessage());
}
return null;
}
At
cipher.doFinal()
I got the following Exception:
javax.crypto.badpaddingexception pad block corrupted
I went through my post but ended up with no solution. I am badly stuck over here.
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG","Crypto");
works perfectly
Note: This code works only on devices up to Android 6. Starting with Android 7.0 the "Crypto" provider has been removed, therefore this code will fail.
AES keys should consist of random data. If you store them as a String then you are likely to loose information, especially if you use encodings such as UTF-8. Your line:
AppConstants.AESEncryptionKey.getBytes("UTF8")
Makes it likely that you've lost data during conversion to/from a string. Use hexadecimals instead if you require a string, or simply store the key as a byte array.
Note that this answer doesn't indicate any security related hints. In general you only want to derive keys or store them in containers. You don't want to use CBC over an insecure channel either.
In my case issue is came because encrypted key and decrypted key both are different, when I check both key with same value then issue is not came

Decrypt file in parts

So I have these large files (6GB+) that I need to decrypt on a 32 bit computer. The general procedure that I used previously was to read the entire file in memory, then pass it on to the decrypt function and then write it all back to a file. This doesn't really work due to memory limitations. I did try passing the file in parts to the decrypt function but it seems to mess up around the boundaries of where I break up the file before sending it to the decrypt function.
I've tried breaking up the file in parts relative to key size but that doesnt seem to matter. I tried a byte array of size 2048 as well as a byte aray of size 294 thinking that might be the special boundary but, no luck. I can see parts of the file correctly decrypted but parts which are total gibberish.
Is it just NOT POSSIBLE to decrypt the file in chunks? If there is a way, then how?
Here is my decryption function / my attempt to decrypt in parts.
private Path outFile;
private void decryptFile(FileInputStream fis, byte[] initVector, byte[] aesKey, long used) {
//Assume used = 0 for this function.
byte[] chunks = new byte[2048]; //If this number is greater than or equal to the size of the file then we are good.
try {
if (outFile.toFile().exists())
outFile.toFile().delete();
outFile.toFile().createNewFile();
FileOutputStream fos = new FileOutputStream(outFile.toFile());
OutputStreamWriter out = new OutputStreamWriter(fos);
IvParameterSpec spec = new IvParameterSpec(Arrays.copyOfRange(initVector, 0, 16));
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, spec);
int x;
while ((x = fis.read(chunks, 0, chunks.length)) != -1) {
byte[] dec = cipher.doFinal(Arrays.copyOfRange(chunks, 0, x));
out.append(new String(dec));
}
out.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
LOG.error(ExceptionUtils.getStackTrace(e));
}
}
Consider using Cipher#update(byte[], int, int, byte[], int) instead of doFinal() for multipart operations. This will take care of part boundaries for you.
The last part of the deciphered data can be obtained by calling the doFinal(byte[] output, int outputOffset) method.

Passing gzencoded data to mcrypt_encrypt, can't unzip after decryption

To keep this short, are there known issues when passing results of gzencode (or other non-text data) to mcrypt_encrypt functions?
Details:
Basically I have an issue where encryption/decryption is working for plain text but I get errors unzipping if I pass compressed data to the encryption function, then decrypt and unzip.
So in PHP, I'm passing results of gzencode() to the encrypt function. Then I base64 encode for showing results on a web service web page. Then in a Java app I'm decoding base64, decrypting, and unzipping using GZIPInputStream. I get errors in the last step.
But everything works fine if I skip the compression step (just pass plain text to the encrypt function). Everything also works fine if I skip encryption and just do compression. So those functions seem to work fine on both PHP and Java sides if I don't combine them.
public static function encrypt($str,$key,$iv) {
$str=Crypto2::pkcs5Pad($str,mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
$encrypted=mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$str,MCRYPT_MODE_CBC,$iv);
return $encrypted;
}
public static function pkcs5Pad ($text, $blocksize) {
$pad = $blocksize - (strlen($text) % $blocksize);
$padded=$text . str_repeat(chr($pad), $pad);
return $padded;
}
Java functions:
public static byte[] decrypt(byte[] inputbuffer) throws Exception {
Key key = new SecretKeySpec(keybyte, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, key, ivSpec);
c.getBlockSize();
System.out.println("Block size="+c.getBlockSize());
int outlen = c.getOutputSize(inputbuffer.length);
System.out.println("Output length will be:"+outlen);
byte[] result=c.doFinal(inputbuffer);
return result;
}
public static byte[] decodeBase64(String data) throws IOException{
BASE64Decoder decoder = new BASE64Decoder();
byte[] decodedBytes = decoder.decodeBuffer(data);
return decodedBytes;
}
public static void unzipPrint(byte[] data) throws Exception{
InputStream is=new GZIPInputStream(new ByteArrayInputStream(data));
int ch2;
while((ch2=is.read())!=-1) {
System.out.print((char)ch2);
}
}
So if I do this in PHP:
base64_encode(encrypt(gzencode($plain_text)));
and this in Java
unzipPrint(decrypt(decodeBase64(data)));
I get the dreaded: "java.util.zip.ZipException: oversubscribed dynamic bit lengths tree" during the unzip phase.
Again, if I skip the compression/decompression steps at both ends everything is fine. And if I skip encryption at both ends then compression/decompression works fine.
EDIT: Ok weird, but after checking byte by byte the resulting byte array of the compressed data (after decoding base64 and decryption), I found a SINGLE byte that was off (compared to the original PHP byte array) by a value of 1. It was byte number 14 (index 13 in Java) and it had a value of 110 instead of 111. I have absolutely no idea how this could be the case.
So if I change that single byte to from 110 to 111, then I can successfuly use GZIPOutputStream to uncompress the data.
So I know what is wrong but not why.
EDIT 2: This is SOLVED ->Thanks to comment by Owlstead I double checked the IV values and found that there was minor descrepancy between the php and java code. How this can lead to only one byte of difference in the resulting decrypted data I have no idea.
That was one wasted day over a single 0x13 instead of 0x12 in my IV.
You should check the IV, as it may change only the first block of cipher text, which holds the ZIP header.
(to close the question, glad you got it solved, any day that you solve an issue is not one wasted in my opinion :) )

How can I decrypt in objective-c from a file encrypted by Java Cipher

I have an encrypt method in Java.
public static String encrypt(String orignal){
SecretKeySpec key = new SecretKeySpec(keyString.getBytes(), "AES");
IvParameterSpec initalVector = new IvParameterSpec(initialVectorParam.getBytes());
try{
Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
/////////////// encrypt /////////////////
cipher.init(Cipher.ENCRYPT_MODE, key, initalVector);
Log.d("AES", "oriByte: "+ orignal.getBytes());
int length = orignal.length();
for(int i=0; i<length; i++){
}
byte[] test = cipher.doFinal(orignal.getBytes());
Log.d("AES", "encByte: "+ test);
return bytes2Hex(test);
}catch (Exception e) {
Log.d("AES", "Encrypt Exception:"+orignal);
return "";
}
}
For compatibility with PHP, I use "AES/CFB8/NoPadding" options.
In PHP: $sCipher = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $sKey, $sStr, MCRYPT_MODE_CFB, $sIV);
And I have a Objective-c Cipher code from here.
https://gist.github.com/838614
I found that there is no IvParameterSpec in Objective-c Cipher like java.
Besides, the getBytes method returns a different value with java.
(I think this is because java uses different encoding way.)
So, how can I apply IvParameterSpec in Objective-c.
And is there any way to get 'getBytes' value like java in Objective-c?
For the initialization vector, see line 24 in your pastie:
NULL /* initialization vector (optional) */,
That's where you would pass your IV.
But without knowing the string encoding the Java code used to create the bytes used as the IV, you won't be able to seed the encryption properly to decrypt the data, even if you know what the string displays to the screen as. Put another way, just because the IV looks like "abc123" doesn't mean the bytes Java is writing to the IV buffer are going to be the same bytes you'll get if you strncpy() from a C character literal buffer. You have to agree on the encoding as part of the protocol for handling the data.
You will also need to agree on a key size. Your Java code does not specify how many bits are in the AES key.
Once you've got that worked out, you'll want to use a call like:
const void *key = /* KEY BYTES */;
const void *iv = /* IV BYTES */;
const void *text = /* CIPHER TEXT */;
size_t textlen = /*...*/;
size_t outlen = 0;
(void)CCCrypt(kCCDecrypt, kCCAlgorithmAES128, 0/*use CBC mode*/,
key, kCCKeySizeAES128, iv,
text, textlen,
&text, textlen, &outlen);
The plaintext will be written over the ciphertext, assuming all goes well. The amount o data written to text during decryption will be stored in outlen. Error checking is your responsibility; the header is well-commented.
Once you have the data, you'll want to slurp it into an NSString with the correct encoding (+[NSString initWithData:encoding:] would work), and then you have a string you can work with from Obj-C like any other string.

Categories

Resources