• Java AES加解密报错javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher的问题原因及2种解决方式


    一、问题背景及原因分析

      需求对保密性要求严格点,就用的 AES + 盐值 + 偏移向量 去做,前端加密传递参数,Java 解密参数,然后查询数据,得到数据后再将数据加密返给前端,前端最对数据进行解密,得到具体数据使用。

      在此过程中发现偶尔使用 Java AES 解密前端传递的参数时会报这个异常,如下:

    javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
        at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:922)
        at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:833)
        at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
        at javax.crypto.Cipher.doFinal(Cipher.java:2165)
        at com.symmetric.aes.TestAES.testDecrpyt(TestAES.java:200)
        at com.symmetric.aes.TestAES.main(TestAES.java:48) 

      字面理解很容易,就是解密的字符串的数组必须是 16 的倍数。

      这里有一篇文章介绍:https://blog.csdn.net/kzcming/article/details/80019478,可以看一下。

    1、分析出现此异常的情况:

      如果不把加密后的数组拼接为字符串,直接返回,然后使用这个加密后的数组进行解密就没有任何错误;

      但是把加密后的数组拼接为字符串,然后解密时在把此字符串转为数组,就会出现此异常

    2、具体分析:

      发现当把字节数组转为字符串后,在把 字符串.getBytes() 获得字节数组,发现两个字节数组前后不一样了  ——  这是报错的位置所在。(声明:new String(byte[]) 和 "str".getBytes() 两个方法使用的编码一样,然后换成其他编码也出现这样情况,也就是说不是编码的问题)

    3、原因

    (1)为什么数组转字符串,字符串然后转数组会出现,前后两个字节数组的值会不同?

      因为并不是每个字节数和编码集上的字符都有对应关系,如果一个字节数在编码集上没有对应,编码 new String(byte[])  后往往解出来的会是一些乱码无意义的符号,例如:��。

      但是解码的时候 � 这个字符也是一个字符在编码表中也有固定的字节数用来表示,所有解码出来的值必定是编码表中对应的值,除非你的字节数组中的字节数正好在编码表中有对应的值,否则编码、解码后的字节数组会不一样。

      误区:误以为所有的字节数组都可以new String(),然后在通过String.getBytes()还原。

    (2)再说这个异常报错:解密的字节数组必须是16的倍数,这得从AES的原理说起,AES是把数据按16字节分组加密的,所有如果数组长度不是16的倍数会报错。

      AES原理:AES是对数据按128位,也就是16个字节进行分组进行加密的,每次对一组数据加密需要运行多轮,而输入密钥的长度可以为128、192和256位,也就是16个字节、24个字节和32个字节,如果用户输入的密钥长度不是这几种长度,也会补成这几种长度。

      无论输入密钥是多少字节,加密还是以16字节的数据一组来进行的,密钥长度的不同仅仅影响加密运行的轮数。

    4、解决的办法:

    (1)可以用 base64 对产生的数组进行编码,然后在解码,这样不会像 new String(byte[])、getBytes() 那样造成数组前后不一致,一开始我看到大部分人都是用 base64,我也只是以为多一层编码看起来安全一些而已,没想到 base64 对数组的处理是不会造成误差的

    (2)就是直接返回数组,然后再用数组解密咯

    二、解决方案

      而我本身就是采用了base64 编码的,结果还是偶尔出现这个报错,后来发现了规律,就是只有前端加密的字符串包含特殊字符,如 + ,传递给后台去解密就一定会报这个错。而我本身就进行了 encodeURIComponent() 进行传参。

      后来了解到原来原因在这里:由于前台通过 url 传过来的加密后的数据到后台接受丢失特殊字符(url 对字符串进行编码,但是发现+全部都变成了空格),然后断点调试一下,确实 + 变成了 空格

    1、解决方式一:Get 参数需要对 URL特殊字符进行转义

    // 对前台的代码进行编码
    bankCardNumber = bankCardNumber.replace(/\+/g,"%2B");
    
    // 后台再转码回去 - 就是替换
    encrypted = encrypted.replaceAll("%2B", "\\+");

    (1)知识

    1、URL特殊字符需转义 
    2、空格换成加号(+)   
    3、正斜杠(/)分隔目录和子目录   
    4、问号(?)分隔URL和查询   
    5、百分号(%)制定特殊字符   
    6、#号指定书签   
    7、&号分隔参数 

    (2)转义字符的原因:

      如果你的表单使用get方法提交,并且提交的参数中有“&”等特殊符的话,如果不做处理,在service端就会将&后面的作为另外一个参数来看待。例如表单的action为list.jsf?act=Go&state=5,则提交时通过request.getParameter可以分别取得act和state的值。 如果你的本意是act='go&state=5'这个字符串,那么为了在服务端拿到act的准确值,你就必须对&进行转义。

    (3)url 转义字符原理:

      将这些特殊的字符转换成ASCII码,格式为:%加字符的ASCII码,即一个百分号%,后面跟对应字符的ASCII(16进制)码值。例如 空格的编码值是"%20"。

    (4)URL特殊符号及对应的十六进制值编码:

    1、+  URL 中+号表示空格 %2B 

    2、空格 URL中的空格可以用+号或者编码 %20  

    3、/ 分隔目录和子目录 %2F   

    4、? 分隔实际的 URL 和参数 %3F   

    5、% 指定特殊字符 %25   

    6、# 表示书签 %23   

    7、& URL 中指定的参数间的分隔符 %26   

    8、= URL 中指定参数的值 %3D

    2、解决方式二:使用 post 在 body 里传参即可(这样就不会存在需 URL 转义的问题)

    @PostMapping("/downUrl")
    public OperationInfo getDownUrl (@RequestBody String jsonStr) throws Exception{
      JSONObject jsonObject = JSONObject.parseObject(jsonStr);
      String idStr = jsonObject.getString("idStr");
      ......
    }

      这里就涉及到 Java 对象与 JSON 对象之间的相互转换。

  • 相关阅读:
    信息的表示和处理
    Linux基础与Linux下C语言编程基础
    Linux基础入门
    第4次实验
    第三次实验
    第二次实验
    java第一次实验
    数据库提示日志文件不可用
    SQL Server 2012 列存储索引分析(翻译)
    修改delphi xe6 FMX Label字体颜色
  • 原文地址:https://www.cnblogs.com/goloving/p/15574685.html
Copyright © 2020-2023  润新知