Search code examples
javaencryptioncryptographyaes

Why am I getting javax.crypto.BadPaddingException (AES/CBC/PKCS5Padding)


I'm trying to get some encryption/decryption going using AES/CBC/PKCS5Padding and am getting a strange result. Depending on the original value I use to encrypt I get an exception:

javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

To test this out, I wrote a little function that starts with a string and progressively makes it bigger, trying to encrypt the string and decrypt the encrypted result in each iteration.

  • First iteration ==> string == "5" Encrypt and decrypt
  • Next iteration ==> string == "55" Encrypt and decrypt
  • Next iteration ==> string == "555" Encrypt and decrypt
  • Next iteration ==> string == "5555" Encrypt and decrypt
  • Next iteration ==> string == "55555" Encrypt and decrypt

If consistently fails to decrypt the encrypted values in items 0 and 4 (first and last). It successfully decrypts the other values.

Any clues what may be causing this?

Here is the output of the program:

0 **************************************

ENCRYPT Key: [00000000000000000000000000000000] value: [5]
This is the ciphertext encrypted [ÂZ??¢?»NÔå?Ó^Ç ]
Encrypted Value = [C25A863FA23FBB4ED4E53FD35E7FC7A0]
DECRYPT Key: [00000000000000000000000000000000] value: [C25A863FA23FBB4ED4E53FD35E7FC7A0]
This is the ciphertext [[B@5fdef03a]
javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
        at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:977)
        at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1058)
        at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:855)
        at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
        at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2205)
        at com.mgl.siebel.crypt.AES256Crypt.decrypt(AES256Crypt.java:35)
        at com.mgl.siebel.crypt.AES256Crypt.test2(AES256Crypt.java:85)
        at com.mgl.siebel.crypt.AES256Crypt.main(AES256Crypt.java:101)
Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

1 **************************************

ENCRYPT Key: [00000000000000000000000000000000] value: [55]
This is the ciphertext encrypted []­*çü×z%?eÑ¥zx~÷]
Encrypted Value = [5DAD2AE7FCD77A259665D1A57A787EF7]
DECRYPT Key: [00000000000000000000000000000000] value: [5DAD2AE7FCD77A259665D1A57A787EF7]
This is the ciphertext [[B@5ccd43c2]
Decrypted Value = [55]

2 **************************************

ENCRYPT Key: [00000000000000000000000000000000] value: [555]
This is the ciphertext encrypted [M÷o?gI¶àeØÖ8c.+]
Encrypted Value = [4DF76F916749B6E065D807D638632E2B]
DECRYPT Key: [00000000000000000000000000000000] value: [4DF76F916749B6E065D807D638632E2B]
This is the ciphertext [[B@4aa8f0b4]
Decrypted Value = [555]

3 **************************************

ENCRYPT Key: [00000000000000000000000000000000] value: [5555]
This is the ciphertext encrypted [ÖFè7tÔ·ðGÂ?WÂGs ]
Encrypted Value = [D646E83774D4B7F047C28657C24773A0]
DECRYPT Key: [00000000000000000000000000000000] value: [D646E83774D4B7F047C28657C24773A0]
This is the ciphertext [[B@7960847b]
Decrypted Value = [5555]

4 **************************************

ENCRYPT Key: [00000000000000000000000000000000] value: [55555]
This is the ciphertext encrypted [ȱiã?'èÀ­0<eäy?]
Encrypted Value = [C80EB169E33F27E8C0AD303C65E4791B]
DECRYPT Key: [00000000000000000000000000000000] value: [C80EB169E33F27E8C0AD303C65E4791B]
This is the ciphertext [[B@2aae9190]
javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
        at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:977)
        at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1058)
        at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:855)
        at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
        at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2205)
        at com.mgl.siebel.crypt.AES256Crypt.decrypt(AES256Crypt.java:35)
        at com.mgl.siebel.crypt.AES256Crypt.test2(AES256Crypt.java:85)
        at com.mgl.siebel.crypt.AES256Crypt.main(AES256Crypt.java:101)
Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

Here is the code being executed


  public String decrypt(String key, String encryptedRawValue) throws Exception {
        System.out.println("DECRYPT Key: [" + key + "] value: [" + encryptedRawValue + "]");
        try {
          if ((key == null) || (encryptedRawValue == null)) {
            throw new Exception("key and value must not be null");
          } 
          
          // convert raw value into its original encrypted sequence of bytes
          byte[] ciphertext = DatatypeConverter.parseHexBinary(encryptedRawValue);
          System.out.println("This is the ciphertext [" + ciphertext + "]");
          byte[] raw = key.getBytes(Charset.forName("UTF-8"));
          if (raw.length != 32) {
            throw new IllegalArgumentException("Invalid key size.");
          }
          SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
          
          Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
          cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
          byte[] original = cipher.doFinal(ciphertext);
          String plainTextValue = new String(original, Charset.forName("UTF-8"));
          return (plainTextValue);
        } catch (Exception e) {
          e.printStackTrace();
          throw e;
        }
      }
      
  public String encrypt(String key, String value) throws Exception {
        System.out.println("ENCRYPT Key: [" + key + "] value: [" + value + "]");
    try {
      byte[] raw = key.getBytes(Charset.forName("UTF-8"));
      if (raw.length != 32) {
        throw new Exception("Invalid key size.");
      }
  
      SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
      cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
      String encryptedValue = new String(cipher.doFinal(value.getBytes(Charset.forName("UTF-8"))));
      System.out.println("This is the ciphertext encrypted [" + encryptedValue + "]");

      String rawValue = DatatypeConverter.printHexBinary(encryptedValue.getBytes());
      return (rawValue);
    } catch(Exception e) {
      e.printStackTrace();
      throw e;
    }
  }


  private void test2() throws Exception {
    String key = "00000000000000000000000000000000";
    try {
      String value = "";
      for (int i=0; i < 5; i++) { // loop 5 times encrypting and decrypting 
        System.out.println("\n" + i + " **************************************\n");
        try {
          value = value + "5";
          String encryptedValue = this.encrypt(key, value);
          System.out.println("Encrypted Value = ["+ encryptedValue + "]");
    
          String plainTextValue = this.decrypt(key, encryptedValue);
          System.out.println("Decrypted Value = ["+ plainTextValue + "]");

        } catch(Exception e) {
          System.out.println(e.getMessage());
        }

      }
    } catch(Exception e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    AES256Crypt c = new AES256Crypt();
    try {
      c.test2();
    } catch(Exception e) {
      e.printStackTrace();
    }
  }


}

Solution

  • String encryptedRawValue

    This cannot work. Strings are a sequence of characters. Encrypted data is a sequence of bytes. If we live in magic unicornland where unicode, and more generally western characters, can just be waved away as being non-existent, you can write really bad code and conflate the two. This was common in ye olden days. it's so bad, it's a major reason for why python 2 decided to up and ditch it all and move to python 3.

    There is only one fix. Stop doing this. The correct type is byte[]. If you then need this byte[] rendered in string form for some reason, then the only sane reason is because it needs to be rendered in a highly limited venue, such as an email. In which case, you should base64 encode it. Search the web for 'java base64' on how to do this, if you must. Those APIs get it right: The 'encode' method takes a byte[] and returns a String, and the decode method takes a String and returns a byte[].

    Fix this issue and the problem goes away.

    String encryptedValue = new String(cipher.doFinal(value.getBytes(Charset.forName("UTF-8"))));

    Do not wrap the result of doFinal into new String. Take the byte array. That's your data.