使用高版本的jdk1.8解決AES256加密問題,至少需要 1.8.0_162或以上版本
轉載自:https://www.cnblogs.com/xxoome/p/13927481.html
Java version: 1.8.0_151-b12
AES(Advanced Encryption Standard)加密算法屬於對稱加密算法,AES加密算法的安全性要高於DES和3DES, 所以AES已經成為了主要的對稱加密算法.
AES的加密流程
要理解AES的加密流程, 會涉及到AES的五個關鍵詞: 分組密碼體制, Padding, 初始向量IV, 密鑰, 加密模式.
-
分組密碼體制: 所謂分組密碼體制就是指將明文切成一段一段的來加密, 然后再把一段一段的密文拼起來形成最終密文的加密方
式. AES采用分組密碼體制, 即AES加密會首先把明文切成一段一段的, 而且每段數據的長度要求必須是128位16個字節, 如果最后
一段不夠16個字節了, 就需要用Padding來把這段數據填滿16個字節, 然后分別對每段數據進行加密, 最后再把每段加密數據拼起
來形成最終的密文. -
Padding: Padding就是用來把不滿16個字節的分組數據填滿16個字節用的, 它有三種模式PKCS5、PKCS7和NOPADDING. PKCS5是
指分組數據缺少幾個字節, 就在數據的末尾填充幾個字節的幾, 比如缺少5個字節, 就在末尾填充5個字節的5. PKCS7是指分組數據
缺少幾個字節, 就在數據的末尾填充幾個字節的0, 比如缺少7個字節, 就在末尾填充7個字節的0. NoPadding是指不需要填充, 也就
是說數據的發送方肯定會保證最后一段數據也正好是16個字節. 那如果在PKCS5模式下, 最后一段數據的內容剛好就是16個16怎么辦?
那解密端就不知道這一段數據到底是有效數據還是填充數據了, 因此對於這種情況, PKCS5模式會自動幫我們在最后一段數據后再添
加16個字節的數據, 而且填充數據也是16個16, 這樣解密段就能知道誰是有效數據誰是填充數據了. PKCS7最后一段數據的內容是
16個0, 也是同樣的道理. 解密端需要使用和加密端同樣的Padding模式, 才能准確的識別有效數據和填充數據. 我們開發通常采用
PKCS7 Padding模式. -
初始向量IV: 初始向量IV的作用是使加密更加安全可靠, 我們使用AES加密時需要主動提供初始向量, 而且只需要提供一個初始向
量就夠了, 后面每段數據的加密向量都是前面一段的密文. 初始向量IV的長度規定為128位16個字節, 初始向量的來源為隨機生成. -
密鑰: AES要求密鑰的長度可以是128位16個字節、192位或者256位, 位數越高, 加密強度自然越大, 但是加密的效率自然會低一
些, 因此要做好衡量. 我們開發通常采用128位16個字節的密鑰, 我們使用AES加密時需要主動提供密鑰, 而且只需要提供一個密鑰
就夠了, 每段數據加密使用的都是這一個密鑰, 密鑰來源為隨機生成. -
加密模式: AES一共有四種加密模式, 分別是ECB(電子密碼本模式)、CBC(密碼分組鏈接模式)、CFB、OFB, 我們一般使用的是CBC模
式. 四種模式中除了ECB相對不安全之外, 其它三種模式的區別並沒有那么大. ECB模式是最基本的加密模式, 即僅僅使用明文和密鑰
來加密數據, 相同的明文塊會被加密成相同的密文塊, 這樣明文和密文的結構將是完全一樣的, 就會更容易被破解, 相對來說不是那么
安全, 因此很少使用. CBC模式則比ECB模式多了一個初始向量IV, 加密的時候, 第一個明文塊會首先和初始向量IV做異或操作, 然后
再經過密鑰加密, 然后第一個密文塊又會作為第二個明文塊的加密向量來異或, 依次類推下去, 這樣相同的明文塊加密出的密文塊就是
不同的, 明文的結構和密文的結構也將是不同的, 因此更加安全, 我們常用的就是CBC加密模式.
說完 AES 加密流程, 下面說一說 Java 如何使用 AES 加密.
或許你一直使用 AES-128 加密沒有任何問題, 但當你把密鑰增加到32個字節的時候, 可能會遇到如下異常:
java.security.InvalidKeyException: Illegal key size
To solve that you have to go to this website, download the Unlimited Strength Jurisdiction Policy Files, unzip it, go to the <java-home>/lib/security directory,
and replace the two files local_policy.jar and US_export_policy.jar with the two files from the download.
Starting with Java 1.8.0_151 and 1.8.0_152 there is a new somewhat easier way to enable the unlimited strength jurisdiction policy for the JVM. Without
enabling this you cannot use AES-256. Since this version, it is no longer necessary to download the policy files from the Oracle website and install it. You
can now set the unlimited policy directly in your application with this one-liner:
Security.setProperty("crypto.policy", "unlimited");
In Java 1.8.0_162, the unlimited policy is enabled by default. You no longer need to install the policy file in the JRE or set the security property crypto.policy.
openjdk bugs: Enable unlimited cryptographic policy by default in Oracle JDK builds
Java 使用 AES-256 加密代碼:
1 /** 2 * @author xxx 3 * @date 2020-09-16 11:17 4 **/ 5 public class AES256Util { 6 7 /** 8 * 密鑰, 256位32個字節 9 */ 10 public static final String DEFAULT_SECRET_KEY = "uBdUx82vPHkDKb284d7NkjFoNcKWBuka"; 11 12 private static final String AES = "AES"; 13 14 /** 15 * 初始向量IV, 初始向量IV的長度規定為128位16個字節, 初始向量的來源為隨機生成. 16 */ 17 private static final byte[] KEY_VI = "c558Gq0YQK2QUlMc".getBytes(); 18 19 /** 20 * 加密解密算法/加密模式/填充方式 21 */ 22 private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; 23 24 private static java.util.Base64.Encoder base64Encoder = java.util.Base64.getEncoder(); 25 private static java.util.Base64.Decoder base64Decoder = java.util.Base64.getDecoder(); 26 27 static { 28 java.security.Security.setProperty("crypto.policy", "unlimited"); 29 } 30 31 /** 32 * AES加密 33 */ 34 public static String encode(String key, String content) { 35 try { 36 javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES); 37 javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM); 38 cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI)); 39 40 // 獲取加密內容的字節數組(這里要設置為utf-8)不然內容中如果有中文和英文混合中文就會解密為亂碼 41 byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8); 42 43 // 根據密碼器的初始化方式加密 44 byte[] byteAES = cipher.doFinal(byteEncode); 45 46 // 將加密后的數據轉換為字符串 47 return base64Encoder.encodeToString(byteAES); 48 } catch (Exception e) { 49 e.printStackTrace(); 50 } 51 return null; 52 } 53 54 /** 55 * AES解密 56 */ 57 public static String decode(String key, String content) { 58 try { 59 javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES); 60 javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM); 61 cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI)); 62 63 // 將加密並編碼后的內容解碼成字節數組 64 byte[] byteContent = base64Decoder.decode(content); 65 // 解密 66 byte[] byteDecode = cipher.doFinal(byteContent); 67 return new String(byteDecode, java.nio.charset.StandardCharsets.UTF_8); 68 } catch (Exception e) { 69 e.printStackTrace(); 70 } 71 return null; 72 } 73 74 public static void main(String[] args) { 75 String dbPassword = "123456"; 76 String encryptDbPwd = AES256Util.encode(DEFAULT_SECRET_KEY, dbPassword); 77 System.out.println("encrypt: " + encryptDbPwd); 78 79 String decrypt = AES256Util.decode(DEFAULT_SECRET_KEY, encryptDbPwd); 80 System.out.println("decrypt:" + decrypt); 81 } 82 83 }
測試:
最后特別說明一下:
解密時用到的密鑰, 初始向量IV, 加密模式, Padding模式必須和加密時的保持一致, 否則則會解密失敗.