如何处理“解密时最后一个块不完整”

时间:2011-01-04 21:57:20

标签: android encryption aes

我有一个简单的类来尝试包装加密以便在我的程序中的其他地方使用。

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;
        }
    }
}

它似乎正确地处理加密,但在解密时没那么多,这会在突出显示的行处抛出javax.crypto.IllegalBlockSizeException“最后一个块在解密中不完整”。这是堆栈跟踪:

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)

我已经对我的桌子做了大量的敲击试图弄清楚这一点,但如果我到处都是,它最终会成为一个不同的例外。通过搜索,我似乎找不到多少。

我错过了什么?我将不胜感激任何帮助。

5 个答案:

答案 0 :(得分:19)

我不知道这是IllegalBlockSizeException的问题,但您不应将密钥编码为String,尤其是在未指定字符编码的情况下。如果你想这样做,可以使用像Base-64这样的东西来设计编码任何“二进制”数据,而不是字符编码,它只将某些字节映射到字符。

通常,密钥将包含与默认平台编码中的字符不对应的字节值。在这种情况下,当您创建String时,该字节将被转换为“替换字符”,U + FFFD( ),并且正确的值将无法挽回地丢失。

稍后尝试使用该密钥的String表示将阻止明文被恢复;它可能会导致IllegalBlockSizeException,但我怀疑更有可能出现无效的填充异常。

另一种可能性是源平台和目标平台字符编码是不同的,并且“解码”密文导致字节太少。例如,源编码是UTF-8,并将输入中的两个字节解释为单个字符,而目标编码是ISO-Latin-1,它将该字符表示为单个字节。

答案 1 :(得分:6)

您的getKeySpec()方法错误。您为加密和解密方向生成新的随机密钥。您必须为两者使用相同的密钥。你应该注意到你没有对该方法使用key参数。

答案 2 :(得分:4)

我正在撕裂我的头发,在“坏基础64”和“最后一块不完整”错误之间...当然,这是不对称的。这是我最终做到这一点的本质,希望在讨论中增加更多,而不是我试图解释:

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));
    }
}

答案 3 :(得分:1)

如果您正在处理字节数组,则必须使用相同的缓冲区大小。例如,有bytearray,其大小为1000.加密后,此大小变为2000.(这些不是实际值)。 如果你使用缓冲区来读取所有加密文件,那么你应该选择buffersize到2000.我用这种方式解决了同样的问题。

答案 4 :(得分:0)

对我来说,当要解密的数据被破坏(缺少1个字符)时,我会注意到这个问题。这可能是由于通过WiFi传输数据。