How to allow decryption with the wrong key to complete without throwing a BadPaddingException?

1.2k views Asked by At

I'm in need of a simple AES cryptosystem in ECB. I have one working at the moment in the sense that given the same key twice in a row, it will correctly encrypt and decrypt a message.

However, if I use two different keys for encrypting/decrypting, the program throws a javax.crypto.BadPaddingException: Given final block not properly padded. I need the program to provide an incorrect decryption, presumably something that looks like some encrypted string. Here's my code:

    public static byte[] encrypt(byte[] plaintext, String key) throws Exception {
        char[] password = key.toCharArray();
        byte[] salt = "12345678".getBytes();
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret);
        byte[] ciphertext = cipher.doFinal(plaintext);  
        return ciphertext;
    }

    public static byte[] decrypt(byte[] ciphertext, String key) throws Exception {
        char[] password = key.toCharArray();
        byte[] salt = "12345678".getBytes();
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secret);
        byte[] plaintext = cipher.doFinal(ciphertext);  
        return plaintext;
    }

(Note: I'm aware of the disadvantages of using ECB, salt = "12345678", etc., but it's not my concern at the moment.) Thanks for any and all help.

1

There are 1 answers

0
Duncan Jones On BEST ANSWER

PKCS#5 padding has a very specific structure, so you cannot continue using it if you want decryption with the wrong key to complete without error.

A good way to achieve your goal might be to use a stream mode of operation, rather than a block-mode. In a stream mode, the input key is used to produce a never-ending stream of seemingly random data, which is XORed with the ciphertext to produce plaintext (and vice versa). If you use the wrong key, you get nonsense data out which is the same size as the original plaintext.

Here's a simple example, based on your original code. I use an IV of all zeroes, but you may wish to improve that to be a random value in due course (note: you'll need to store this value with the ciphertext).

public static void main(String[] args) throws Exception {  
  byte[] plaintext = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  byte[] ciphertext = encrypt(plaintext, "foo");

  byte[] goodDecryption = decrypt(ciphertext, "foo");
  byte[] badDecryption = decrypt(ciphertext, "bar");  

  System.out.println(DatatypeConverter.printHexBinary(goodDecryption));
  System.out.println(DatatypeConverter.printHexBinary(badDecryption));
}

public static SecretKey makeKey(String key) throws GeneralSecurityException {
  char[] password = key.toCharArray();
  byte[] salt = "12345678".getBytes();
  SecretKeyFactory factory =
      SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
  KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
  SecretKey tmp = factory.generateSecret(spec);
  return new SecretKeySpec(tmp.getEncoded(), "AES");
}

public static byte[] encrypt(byte[] plaintext, String key) throws Exception {
  SecretKey secret = makeKey(key);
  Cipher cipher = Cipher.getInstance("AES/OFB8/NoPadding");
  cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
  return cipher.doFinal(plaintext);
}

public static byte[] decrypt(byte[] ciphertext, String key) throws Exception {
  SecretKey secret = makeKey(key);
  Cipher cipher = Cipher.getInstance("AES/OFB8/NoPadding");
  cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
  return cipher.doFinal(ciphertext);
}

Output:

00010203040506070809
5F524D4A8D977593D34C