1

I'm trying to decrypt some text and I get an error:

javax.crypto.BadPaddingException: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt com.android.org.conscrypt.NativeCrypto.EVP_CipherFinal_ex(Native Method) com.android.org.conscrypt.OpenSSLCipher.doFinalInternal(OpenSSLCipher.java:430) com.android.org.conscrypt.OpenSSLCipher.engineDoFinal(OpenSSLCipher.java:466)javax.crypto.Cipher.doFinal(Cipher.java:1340)

import android.util.Base64;

import java.security.spec.KeySpec;
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;

public class AESEncrypter {

    private static final byte[] SALT = {
            (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
            (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03
    };
    private static final int ITERATION_COUNT = 65536;
    private static final int KEY_LENGTH = 256;
    private Cipher ecipher;
    private Cipher dcipher;

    public AESEncrypter(String passPhrase) throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), SALT, ITERATION_COUNT, KEY_LENGTH);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // "AES/CBC/NoPadding"
        ecipher.init(Cipher.ENCRYPT_MODE, secret);

        dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] iv = ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
        dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
    }

    public String encrypt(String encrypt) throws Exception {
        encrypt = encrypt.replace("\n", "");

        byte[] bytes = encrypt.getBytes("UTF8");
        byte[] encrypted = encrypt(bytes);
        return Base64.encodeToString(encrypted, Base64.DEFAULT);
    }

    public byte[] encrypt(byte[] plain) throws Exception {
        return ecipher.doFinal(plain);
    }

    public String decrypt(String encrypt) throws Exception {
        encrypt = encrypt.replace("\n", "");
        byte[] bytes = Base64.decode(encrypt, Base64.DEFAULT);
        byte[] decrypted = decrypt(bytes);
        return new String(decrypted, "UTF8");
    }

    public byte[] decrypt(byte[] encrypt) throws Exception {
        return dcipher.doFinal(encrypt);
    }

}

Any suggestions?

8
  • How do you call decrypt? Where did you get the ciphertext from? From the same class? Is it Base64-encoded? Commented Apr 4, 2016 at 8:08
  • @Thilo AESEncrypter encrypter = new AESEncrypter("staticpassword"); and then encrypter.decrypt("encodedstring"); Commented Apr 4, 2016 at 8:59
  • Where did you get "encodedstring" from? Does it look like something encrypt might produce? Is it Base64-encoded? Commented Apr 4, 2016 at 9:21
  • @Thilo, encodedstring it is a previous string that i crypted with String encrypt(String encrypt), yep, it is encoded. Okay.. something like String message = "staticpassword"; String password = "PASSWORD"; AESEncrypter encrypter = new AESEncrypter(password); String encrypted = encrypter.encrypt(message); String decrypted = encrypter.decrypt(encrypted); works, but if i doing the same thing, taking the encrypted string from file, i get an error (JSON parse) Commented Apr 4, 2016 at 10:41
  • 3
    Can you verify that the IV used to decrypt is the same used to encrypt in the case of a file? You probably need to store it along with the ciphertext. Commented Apr 4, 2016 at 11:06

1 Answer 1

2

This answer is a complete shot in the dark, but it is plausible.

Problem:

Currently, you're retrieving the IV as a byte array from the Cipher instance for encryption and passing the same byte array to the Cipher instance for decryption. The problem might be that this IV is not actually copied and will reflect the state of the execution of the encryption procedure. A common CBC implementation might use the byte array that is supposed to be the IV as a state for the encryption of every block. This state would change after encryption.

Thus, if the decryption works on a wrong IV and the original plaintext was shorter than 16 bytes, this will likely (~255 in 256) lead to a BadPaddingException. If the plaintext is 16 bytes or longer, then you will not see a BadPaddingException, but the first 16 bytes will look like garbage. I suggest that you look into how CBC mode works.

Possible solution:

You need to copy the IV. So this should suffice:

dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(Arrays.copyOf(iv, iv.length)));

Proper solution:

Keep in mind that this only works if you encrypt and decrypt during one execution of the program. If you want to decrypt in a different execution, you need to store the IV somehow.

Since the IV does not need to be secret, it can be sent along with the ciphertext. It is customary to prepend it to the ciphertext and slice it off before decryption.

The salt should also be random and you can send it along with the ciphertext in the same way as the IV.


Security consideration:

The ciphertext is not authenticated. Thus, you cannot detect (malicious) manipulation. It is best to use authenticated modes like GCM/EAX or use an encrypt-then-MAC scheme with a strong MAC like HMAC-SHA256.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.