文章目录
- 前后端(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模式)