Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle "last block incomplete in decryption"

I have a simple class to try and wrap encryption for use elsewhere in my program.

import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;

public final class StupidSimpleEncrypter
{
    public static String encrypt(String key, String plaintext)
    {
        byte[] keyBytes = key.getBytes();
        byte[] plaintextBytes = plaintext.getBytes();
        byte[] ciphertextBytes = encrypt(keyBytes, plaintextBytes);
        return new String(ciphertextBytes);
    }

    public static byte[] encrypt(byte[] key, byte[] plaintext)
    {
        try
        {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            SecretKeySpec spec = new SecretKeySpec(getRawKey(key), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, spec);
            return cipher.doFinal(plaintext);
        }
        catch(Exception e)
        {
            // some sort of problem, return null because we can't encrypt it.
            Utility.writeError(e);
            return null;
        }
    }

    public static String decrypt(String key, String ciphertext)
    {
        byte[] keyBytes = key.getBytes();
        byte[] ciphertextBytes = ciphertext.getBytes();
        byte[] plaintextBytes = decrypt(keyBytes, ciphertextBytes);
        return new String(plaintextBytes);
    }

    public static byte[] decrypt(byte[] key, byte[] ciphertext)
    {
        try
        {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            SecretKeySpec spec = new SecretKeySpec(getRawKey(key), "AES");
            cipher.init(Cipher.DECRYPT_MODE, spec);
            return cipher.doFinal(ciphertext);
        }
        catch(Exception e)
        {
            // some sort of problem, return null because we can't encrypt it.
            Utility.writeError(e);
            return null;
        }
    }

    private static byte[] getRawKey(byte[] key)
    {
        try
        {
            KeyGenerator gen = KeyGenerator.getInstance("AES");
            SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
            rand.setSeed(key);
            gen.init(256, rand);
            return gen.generateKey().getEncoded();
        }
        catch(Exception e)
        {
            return null;
        }
    }
}

It seems to handle encryption correctly, but not so much when decrypting, which throws a javax.crypto.IllegalBlockSizeException "last block incomplete in decryption" at the highlighted line. Here is the stack trace:

Location:com.xxxxxx.android.StupidSimpleEncrypter.decrypt ln:49
last block incomplete in decryption
javax.crypto.IllegalBlockSizeException: last block incomplete in decryption
     at org.bouncycastle.jce.provider.JCEBlockCipher.engineDoFinal(JCEBlockCipher.java:711)
     at javax.crypto.Cipher.doFinal(Cipher.java:1090)
     at com.xxxxxx.android.StupidSimpleEncrypter.decrypt(StupidSimpleEncrypter.java:44)
     at com.xxxxxx.android.StupidSimpleEncrypter.decrypt(StupidSimpleEncrypter.java:34)

I have done a good amount of banging my head against my desk to try and figure this out, but if I get anywhere at all, it ends up being a different exception. I also can't seem to find much by searching.

What am I missing? I would appreciate any help.

like image 941
jakebasile Avatar asked Jan 04 '11 21:01

jakebasile


2 Answers

I don't know if this is the problem with the IllegalBlockSizeException, but you should not encode the key as a String, especially without specifying the character encoding. If you want to do this, use something like Base-64, which is designed to encode any "binary" data, rather than a character encoding, which only maps certain bytes to characters.

The key is, in general, going to contain byte values that do not correspond to a character in the default platform encoding. In that case, when you create the String, the byte will be translated to the "replacement character", U+FFFD (�), and the correct value will be irretrievably lost.

Trying to use that corrupt String representation of the key later will prevent the plaintext from being recovered; it is possible it could cause the IllegalBlockSizeException, but I suspect an invalid padding exception would be more likely.

Another possibility is that the source platform and the target platform character encodings are different, and that "decoding" the ciphertext results in too few bytes. For example, the source encoding is UTF-8, and interprets two bytes in the input as a single character, while the target encoding is ISO-Latin-1, which represents that character as a single byte.

like image 124
erickson Avatar answered Sep 21 '22 13:09

erickson


I was tearing my hair out over this, between "bad base 64" and "last block incomplete" errors ... to It is, of course, asymmetrical. Here's the essence how I ended up doing it which hopefully adds more to the discussion than if I attempted to explain:

public String crypto(SecretKey key, String inString, boolean decrypt){
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    byte[] inputByte = inString.getBytes("UTF-8");
    if (decrypt){
        cipher.init(Cipher.DECRYPT_MODE, key);
        return new String (cipher.doFinal(Base64.decode(inputByte, Base64.DEFAULT)));
    } else {
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return new String (Base64.encode(cipher.doFinal(inputByte), Base64.DEFAULT));
    }
}
like image 22
J.B Avatar answered Sep 21 '22 13:09

J.B