简体   繁体   中英

Cipher.doFinal() returning part of decrypted string for a long string

I am experiencing an issue when decrypting a string using sunjce:

javax.crypto.Cipher cipher =
      javax.crypto.Cipher.getInstance("AES/GCM/NoPadding", new BouncyCastleProvider());
  GCMParameterSpec spec = new GCMParameterSpec(Constants.GCM_TAG_BYTES * 8, nonce);
  cipher.init(javax.crypto.Cipher.DECRYPT_MODE, dataKey, spec);

  cipher.update(ciphertext);
  
  return cipher.doFinal();

if I pass the whole ciphertext to doFinal it works correctly but if I call it correctly it only returns partial string. FOr instance for the input

String jsonExample = "{\"dataType\":\"STRING\",\"strValue\":\"000000\"}";

The decrypted bytes only contain "000000" but if I use

return cipher.doFinal(ciphertext); 

and remove the update so it correctly prints the original string. What might be the reason? if I pass an empty byte array to doFinal after the update it also results in the same data loss. I want to know the logic behind it, it passes for small texts but for texts of this size it simply does not work.

this is my input

String jsonExample = "{\"dataType\":\"STRING\",\"strValue\":\"000000\"}";

This is how I am printing the decrypted string

String decryptedString = new String(decrypted, StandardCharsets.UTF_8);

This is how I am passing the input string as bytes to the encrypt function

text = jsonExample.getBytes(StandardCharsets.UTF_8)

this is how I am calling encrypt

GCMParameterSpec spec = new GCMParameterSpec(Constants.GCM_TAG_BYTES * 8, nonce);
try {
  cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, dek, spec);
  byte[] ciphertext = cipher.doFinal(text);

When I use cipher.update(ciphertext) during decryption followed by cipher.doFinal() or cipher.doFInal(new byte[0]) it only returns

"000000" after I use the returned byte[] to String decryptedString = new String(decrypted, StandardCharsets.UTF_8);

But if I directly call cipher.doFInal(cipherText) during decryption the result string I get is the original string.

You're not using SunJCE, you're using the BouncyCastle provider. (You are using the Sun/Oracle API -- JCA, Java Crypto Architecture -- if that's what you meant to say, but not the provider SunJCE.)

Most Ciphers in JCA return partial data from each or any update call (in either encrypt or decrypt direction), thus in general if you use update you must concatenate that value (or those values if multiple calls) plus any value returned from doFinal before using the result for anything that needs it to be complete, such as decoding UTF8. (Typically the easiest way is to .write them all to a ByteArrayOutputStream , or .put them all to a ByteBuffer , but there are other options.) However the SunJCE provider does NOT do this for GCM, in the decryption direction only, apparently because the spec (SP800-38D) calls for the plaintext not to be released if the authentication fails, which can only be determined at .doFinal time, and then it returns all the plaintext.

The Bouncy provider does 'stream' GCM decryption, arguably in violation of the spec but consistent with usual and traditional JCA behavior, so most of the data is returned from the update call with only the last few bytes from doFinal and you need to concatenate these as above -- or, as you've found, just don't use update at all: if the data all fit in one buffer when encrypting, it should also fit when decrypting.

The Bouncy provider must buffer an amount of data equal to the tag so that it can remove and verify said tag; the "small texts" where your code works -- because decrypt .update returns nothing -- will be ones up to but not exceeding your tag size, which is apparently 8 bytes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM