javax.crypto.IllegalBlockSizeException: 在tcp中用padded cipher解密时,输入长度必须是16的倍数

javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher in tcp

我正在使用这种 AES 加密和解密方法来加密我的数据。 udp 没有问题,但是当我使用 tcp 时出现此错误 "javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher"

AES Encryption/Decryption代码:

    public class AESEncDec {

     private static final String ALGO = "AES";
    private static final byte[] keyValue =  new byte[] { 'T', 'h', 'e', 'B','e', 's', 't','S', 'e', 'c', 'r','e', 't', 'K', 'e', 'y' };


public static String encrypt(String Data) throws Exception {
        Key key = generateKey();
        Cipher c = Cipher.getInstance(ALGO);
        c.init(Cipher.ENCRYPT_MODE, key);
        byte[] encVal = c.doFinal(Data.getBytes());
        String encryptedValue = new BASE64Encoder().encode(encVal);
        System.err.println("encVal: "+encryptedValue.length());

        return encryptedValue;
    }

    public static String decrypt(String encryptedData) throws Exception {
        Key key = generateKey();
        Cipher c = Cipher.getInstance(ALGO);
        c.init(Cipher.DECRYPT_MODE, key);
        byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
        byte[] decValue = c.doFinal(decordedValue);
        //byte[] decValue = c.doFinal(encryptedData.getBytes());
        String decryptedValue = new String(decValue);
        System.err.println("decVal: "+decryptedValue.length());

        return decryptedValue;
    }
    private static Key generateKey() throws Exception {
        Key key = new SecretKeySpec(keyValue, ALGO);
        return key;
}

}

TCP 服务器代码:

    class TCPServer
    {
      public static void main(String argv[]) throws Exception
      {
          AESEncDec edData= new AESEncDec();
           // AES edData= new AES();

         String msg="Message_";
         String clientSentence="";
         String capitalizedSentence;
         ServerSocket welcomeSocket = new ServerSocket(6789);
         Socket connectionSocket = welcomeSocket.accept();
          for (int i = 0; i < 10; i++) 
         {
            clientSentence=edData.encrypt(msg+i)+"\n";
            DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
            outToClient.writeBytes(clientSentence);
            Thread.sleep(100);
         }
      }
}

TCP 客户端代码:

    class TCPClient {

    public static void main(String argv[]) throws Exception {

        AESEncDec edData= new AESEncDec();
        String modifiedSentence;
        String DecData="";
        Socket clientSocket = new Socket("localhost", 6789);
        while(true){
         BufferedReader inFromServer = new BufferedReader(new  InputStreamReader(clientSocket.getInputStream()));
         modifiedSentence = inFromServer.readLine();
         DecData=edData.decrypt(modifiedSentence);
         System.out.println("FROM SERVER: " + DecData);   
        }

        //clientSocket.close();
    }
    }

对于相同的代码,当消息很小时,它会被正确解密。但是当消息很长时,我得到 illegalBlocksize 异常。 我尝试使用 AES/CBC/PKCS5Padding 以便填充消息并且块大小为 16 的倍数。但我仍然遇到相同的错误或 BadPaddingException。如果我将 PKCS5Padding 指定为填充技术,则应该正确填充消息并且不应出现此错误。为什么那不起作用。我该怎么做才能正确解密数据。请帮助..

问题是 TCP 给你一个流。如果你读取一个块并尝试解密它,它可能会失败,因为块的大小可能不是 16(或 128)的倍数。 AES 适用于 16 字节或 128 字节的数据块。因此,您可能需要等待一段时间,直到收集到那么多数据才能解密。

由于UDP是面向消息的,所以不会遇到这样的问题。

@Kiara,请使用 Java8 的内置编码,看看它是如何工作的。有关文档,请参阅 here。示例:

String asB64 = Base64.getEncoder().encodeToString(data.getBytes("utf-8")); 

经过测试,它可以很好地处理长度高达 7 兆字节的消息。它没有在编码的消息中引入换行符。

AES 是分组密码,只能将 16 字节精确加密到 16 字节。如果你想加密任意输入,你需要一个操作模式和一个填充方案。默认模式通常是 ECB,默认填充方案是 PKCS#5 填充(实际上是 PKCS#7 填充)。

这意味着单个加密也必须以完全相同的方式解密,因为在解密过程中必须删除填充。重要的是多个密文不重叠并且不被分解(分块/包裹)。
这就是你的换行符在多条消息加密期间应该做的事情,因为你在接收端逐行读取密文。

问题在于默认的 sun.misc.BASE64Encoder#encode 方法实际上会自行引入换行符。每 76 个字符插入一个换行符,但接收方无法知道哪个换行符实际分隔消息密文以及哪个换行符分隔单个消息密文中的换行。

最简单的解决方法是删除 Base64 编码后的换行符:

String encryptedValue = new BASE64Encoder().encode(encVal)<b>.replaceAll("\s", "")</b>;

这将替换单个消息密文中的所有空格。

由于您使用的是私有 sun.misc.* 类,因此最好转移到另一个 Base64 实现,因为那些 类 不必在每个 JVM 中都可用.您应该使用一些已知的库,例如 Apache 的 commons-codec,它提供了一个 implementation,您可以在其中禁用 wrapping/chunking:

String encryptedValue = <b>Base64.encodeBase64(encVal, false)</b>;

相当于

String encryptedValue = <b>Base64.encodeBase64(encVal)</b>;