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 對象之間的相互轉換。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM