文章目錄
- 前后端(JAVA)實現AES對稱加解密方式
- 1 對稱加密分類以及概括
- 1.1 加密安全等級 DES < 3DES < AES < RC
- 1.2 DES
- 1.3 3DES
- 1.4 AES
- 1.5 RC
- 2 前后端實現AES對稱加解密方式
- 3 后端AES對稱加解密(ECB和CBC模式)工具類
- 4 前端(VUE)AES對稱加解密(CBC模式)工具類
- 4.1 aseKeConfig.js AES+CBC配置
- 4.2 加密解密工具類 aesSecretUtil.js
前后端(JAVA)實現AES對稱加解密方式
1 對稱加密分類以及概括
文章《加解密篇 - 對稱加密算法 (DES、3DES、AES、RC)》有對這幾種對稱加密的說明,我大概概括一下。
1.1 加密安全等級 DES < 3DES < AES < RC
依次是DES < 3DES < AES < RC
1.2 DES
DES全稱 Data Encryption Standard,是一種使用密鑰加密的塊算法,該加密算法運用非常普遍,是一種標準的加密算法。現在認為是一種不安全的加密算法,因為現在已經有用窮舉法攻破 DES 密碼的報道了,所以誕生了3DES。
1.3 3DES
3DES(或稱為 Triple DES)是三重數據加密算法(TDEA,Triple Data Encryption Algorithm)塊密碼的通稱。它相當于是對每個數據塊應用三次 DES 加密算法。
1.4 AES
AES 加密算法的安全性要高于 DES 和 3DES,所以 AES 已經成為了主要的對稱加密算法。AES 加密算法就是眾多對稱加密算法中的一種,它的英文全稱是 Advanced Encryption Standard,翻譯過來是高級加密標準,它是用來替代之前的 DES 加密算法的。
AES 一共有四種加密模式,分別是 ECB(電子密碼本模式)、CBC(密碼分組鏈接模式)、CFB、OFB,我們一般使用的是 CBC 模式。四種模式中除了 ECB 相對不安全之外,其它三種模式的區別并沒有那么大。
1.5 RC
RC加密包括RC2,RC4,RC5,RC2 是由著名密碼學家 Ron Rivest 設計的一種傳統對稱分組加密算法,它可作為 DES 算法的建議替代算法。它的輸入和輸出都是64bit。密鑰的長度是從1字節到128字節可變,但目前的實現是8字節(1998年)。
參考:
加解密篇 - 對稱加密算法 (DES、3DES、AES、RC)
2 前后端實現AES對稱加解密方式
-
AES為對稱加密算法,顧名思義,如果是前后端加解密場景,那前端需要保存一份秘鑰,后端也需要保存一份秘鑰,這兩個秘鑰是相同的,才可以實現加解密。
-
AES的秘鑰默認長度為16位,初始向量 IV也是16位,這兩個默認長度一定要遵守,否則會有很多不可未知的錯誤。如果需要增加秘鑰的長度增加復雜性,則推薦使用RC加密算法,因為該算法的秘鑰長度可變。
-
待解密長度需要為16的倍數,否則會報以下錯誤,常用的解決辦法為加密后使用Base64包裝密文,則會自動補齊為16的倍數,解密時先使用Base64解密,則密文一定是16的整數倍。
Input length must be multiple of 16 when decrypting with padded cipher
3 后端AES對稱加解密(ECB和CBC模式)工具類
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;/*** @author * @description AES加解密(ECB和CBC模式)* @date 2023/12/8 15:08*/
public class AESEncryptUtil {/*** 加密模式* ECB: AES/ECB/PKCS5Padding* CBC: AES/CBC/NoPadding*/private static final String[] TRANSFORM_ALGORITHM = new String[]{"AES/ECB/PKCS5Padding", "AES/CBC/NoPadding"};/*** 初始向量樣式 IV*/private static String iv = "HBJNRU56MDk4NzK6";private static final String ALGORITHM = "AES";private static final String CHARSET_NAME = "UTF-8";/*** AES加解key樣式*/public static String ENCRYPT_KEY = "7CC408B24462ABD1";/*** AES的ECB模式加解* @param data 待加密參數* @param key 加密key* @return*/public static String encryptECB(String data, String key) {if (StringUtils.isEmpty(key)) {throw new IllegalArgumentException("加密失敗,加密key為空");}SecretKeySpec aesKey = new SecretKeySpec(key.getBytes(Charset.forName(CHARSET_NAME)), ALGORITHM);try {Cipher cipher = Cipher.getInstance(TRANSFORM_ALGORITHM[0]);cipher.init(Cipher.ENCRYPT_MODE, aesKey);byte[] encrypted = cipher.doFinal(data.getBytes(Charset.forName(CHARSET_NAME)));// 使用Base64來包裝是規避報錯Input length must be multiple of 16 when decrypting with padded cipher// 解密的字節數組必須是16的倍數return Base64.getEncoder().encodeToString(encrypted);} catch (Exception e) {throw new IllegalArgumentException("加密失敗: "+ e.getMessage());}}/*** AES的ECB模式解密* @param data 待解密參數* @param key 解密key* @return*/public static String decryptECB(String data, String key) {if (StringUtils.isEmpty(key)) {throw new IllegalArgumentException("解密失敗,解密key為空");}byte[] decode = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8));SecretKeySpec aesKey = new SecretKeySpec(key.getBytes(Charset.forName(CHARSET_NAME)), ALGORITHM);try {Cipher cipher = Cipher.getInstance(TRANSFORM_ALGORITHM[0]);cipher.init(Cipher.DECRYPT_MODE, aesKey);return new String(cipher.doFinal(decode));} catch (Exception e) {throw new IllegalArgumentException("解密失敗: "+ e.getMessage());}}/*** AES的CBC模式加密* @param data 要加密的數據* @param key 加密key* @return 加密的結果*/public static String encryptCBC(String data, String key) {try {// "算法/模式/補碼方式"Cipher cipher = Cipher.getInstance(TRANSFORM_ALGORITHM[1]);int blockSize = cipher.getBlockSize();byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);int plaintextLength = dataBytes.length;if (plaintextLength % blockSize != 0) {plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));}byte[] plaintext = new byte[plaintextLength];System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);SecretKeySpec keyStr = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");IvParameterSpec ivStr = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));cipher.init(Cipher.ENCRYPT_MODE, keyStr, ivStr);byte[] encrypted = cipher.doFinal(plaintext);return Base64.getEncoder().encodeToString(encrypted);} catch (Exception e) {throw new IllegalArgumentException("加密失敗: "+ e.getMessage());}}/*** AES的CBC模式解密* @param data 要解密的數據* @param key 解密key* @return 解密的結果*/public static String decryptCBC(String data, String key) {try {byte[] encrypted1 = Base64.getDecoder().decode(data);Cipher cipher = Cipher.getInstance(TRANSFORM_ALGORITHM[1]);SecretKeySpec keyStr = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");IvParameterSpec ivStr = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));cipher.init(Cipher.DECRYPT_MODE, keyStr, ivStr);byte[] original = cipher.doFinal(encrypted1);return new String(original, StandardCharsets.UTF_8);} catch (Exception e) {throw new IllegalArgumentException("解密失敗: "+ e.getMessage());}}public static void main(String[] args) throws Exception {String data = "hello Test symmetric encry";String keyStr = "7CC408B24462ABD1";String encryDataStr = encryptECB(data, keyStr);System.out.println("encryptECB = " + encryDataStr);System.out.println("decryptECB = " + decryptECB(encryDataStr, keyStr));encryDataStr = encryptCBC(data, keyStr);System.out.println("encryptCBC = " + encryDataStr);System.out.println("decryptCBC = " + decryptCBC(encryDataStr, keyStr));}}
4 前端(VUE)AES對稱加解密(CBC模式)工具類
前端其他工程配置請看文章《java前后端參數和返回加密解密AES+CBC》
npm install crypto-js
4.1 aseKeConfig.js AES+CBC配置
export const AES_KEY = 'MTIzNDU2Nzg5MEFC'
export const AES_IV = 'QUJDRURGMDk4NzY1'
// 參數是否進行加密設置,需要與后端配置保持一致
export const PARAM_ENCRYPT_ABLE = true
// 結果是否進行加密
export const RESULT_ENCRYPT_ABLE = true
// 需要排除的不進行加密的接口,正則匹配
export const EXCLUE_PATH = ['.*/orc/.*', '.*/fastdfs/.*', '.*/eempFastdfs/.*', ',.*/homepage/preview', '.*oauth/getClickApplicationInfo', '.*/common/defaultKaptcha.*', '.*/autoKeyword$', '.*/getLayerCount$', '.*/getLayerInfoListByPage$','.*/epUiImgSaveAndAnalysisMultipartFile$', '/v7/weather']
4.2 加密解密工具類 aesSecretUtil.js
import CryptoJS from 'crypto-js'
import {AES_KEY,AES_IV,PARAM_ENCRYPT_ABLE,EXCLUE_PATH,RESULT_ENCRYPT_ABLE
}
from '../config/aesKeyConfig.js'
const key = CryptoJS.enc.Utf8.parse(AES_KEY) // 16位const iv = CryptoJS.enc.Utf8.parse(AES_IV)const excluePath = EXCLUE_PATHconst paramEncryptAble = PARAM_ENCRYPT_ABLEconst resultEncryptAble = RESULT_ENCRYPT_ABLE/***Description AES CBC BASE64加密解密*@author*@date 13:38 2022/3/31*/export default {// aes加密encrypt(word) {let encrypted = ''if (typeof word === 'string') {const srcs = CryptoJS.enc.Utf8.parse(word)encrypted = CryptoJS.AES.encrypt(srcs, key, {iv: iv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.ZeroPadding})} else if (typeof word === 'object') {// 對象格式的轉成json字符串const data = JSON.stringify(word)const srcs = CryptoJS.enc.Utf8.parse(data)encrypted = CryptoJS.AES.encrypt(srcs, key, {iv: iv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.ZeroPadding})}return CryptoJS.enc.Base64.stringify(encrypted.ciphertext)},// aes解密decrypt(word) {if (word) {let base64 = CryptoJS.enc.Base64.parse(word)let src = CryptoJS.enc.Base64.stringify(base64)var decrypt = CryptoJS.AES.decrypt(src, key, {iv: iv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.ZeroPadding})var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)return decryptedStr.toString()} else {return word}},// 判斷url是否在匹配的正則表達式上,匹配則不進行加密,不配則需要加密checkIsExcluePath(url) {// 如果包含需要排除加密的接口返回truelet flag = falsefor (let i = 0; i < excluePath.length; i++) {if (new RegExp('^' + excluePath[i]).test(url)) {flag = truebreak} else {flag = false}}return flag},// 判斷是否請求需要進行加密,配置值true的時候需要加密否則不需要checkParamEncryptAble() {// console.log(encryptAble)return paramEncryptAble},// 判斷是否只對結果進行加密checkResultEncryptAble() {// console.log(encryptAble)return resultEncryptAble}}
參考:
java前后端參數和返回加密解密
對稱加密( 共享密鑰密碼 ) —用相同的密鑰進行加密和解密
手寫一個java加密工具類Securit
Java中的AES加解密(CBC模式)