相关系列文章
引言
AES(Advanced Encryption Standard,高级加密标准)
是一种广泛使用的对称分组加密算法,它使用相同的密钥进行加密和解密操作,以其高安全性、高效率和支持多种密钥长度的特点,成为保护数据安全的基石技术之一。
AES的设计基于替代-置换网络(SPN)结构,能够有效抵抗多种密码攻击,保障数据在传输和存储过程中的机密性与完整性。
一、AES算法核心原理
AES是一种分组密码算法,它将明文数据分成固定长度的块进行处理,每个块的大小为128位(16字节)。其加密过程通过多轮循环的替换和置换操作实现。
1. 密钥与轮数
AES支持三种不同长度的密钥,每种密钥对应不同的加密轮数:
• AES-128:128位密钥,10轮加密
• AES-192:192位密钥,12轮加密
• AES-256:256位密钥,14轮加密
密钥长度越长,安全性越高,但计算开销也相应增加。
2. 加密过程
AES的加密过程包含四个核心操作,每轮都会执行这些操作(最后一轮略有不同):
字节替换(SubBytes):通过一个称为S盒(Substitution box)的非线性替换表,将输入数据的每个字节替换为另一个字节,增加算法的非线性特性,增强安全性。
行移位(ShiftRows):将数据矩阵的每一行进行循环左移操作:第一行不变,第二行左移1字节,第三行左移2字节,第四行左移3字节。这打破了列的独立性,增强了扩散效果。
列混淆(MixColumns):将每列视为有限域GF(2^8)上的多项式,与一个固定多项式进行模乘运算,使每个输出字节依赖于输入列中的所有字节,实现列内的扩散(最后一轮省略此步骤)。
轮密钥加(AddRoundKey):将当前数据状态与当前轮的轮密钥进行简单的按位异或(XOR)操作。轮密钥由初始密钥通过密钥扩展算法生成,每一轮使用的轮密钥都不同。
3. 密钥扩展
密钥扩展算法将初始密钥扩展成多个轮密钥(子密钥)。扩展过程使用非线性变换和循环移位操作,确保每轮加密使用不同的密钥,极大增强了安全性。
4. 解密过程
AES的解密过程是加密过程的逆操作,使用相同的密钥,但以相反的顺序执行逆操作(InvSubBytes、InvShiftRows、InvMixColumns)和应用轮密钥。
二、AES的工作模式与填充
1. 常见工作模式
AES有多种工作模式,以适应不同的应用场景:
• ECB(电子密码本):最简单的工作模式,但安全性较低,相同的明文会生成相同的密文。
• CBC(密码块链接):引入初始化向量(IV),增强安全性,是广泛使用的模式之一。
• CTR(计数器模式):将块密码转换为流密码,适用于流加密场景。
• GCM(伽罗瓦/计数器模式):提供加密和认证功能,适用于需要保证数据完整性和机密性的场景。
工作模式 | 全称 | 特点 | 适用场景 |
---|---|---|---|
ECB | Electronic Codebook | 简单,并行计算,相同明文生成相同密文,安全性较低 | 简单数据加密,不推荐用于加密大量重复模式的数据 |
CBC | Cipher Block Chaining | 引入初始化向量(IV),链接模式,安全性高于ECB | 文件加密,SSL/TLS |
CTR | Counter | 将分组密码转换为流密码,并行加密,需唯一计数器 | 实时流媒体加密,高速网络通信 |
GCM | Galois/Counter Mode | 提供加密和认证功能,高效并行处理 | 需要同时保证机密性和完整性的场景,如SSH |
2. 填充模式
当数据长度不是128位(16字节)的整数倍时,需要进行填充。常见的填充模式包括:
• PKCS5/PKCS7 Padding:最常用的填充方案。如果需要填充n个字节,则每个填充字节的值都是n。
• Zero Padding:用0x00字节填充不足的部分。需注意无法区分填充值和实际数据,可能需额外记录数据长度。
• No Padding:要求明文长度必须是块大小的整数倍。
三、SpringBoot 集成 AES 加密算法实战
下文 以 Java AES/CBC/PKCS5Padding
为例,实现 加密解密
PKCS5Padding 和 PKCS7Padding 填充方式一致
1. Maven 依赖
首先,在项目的 pom.xml文件中添加以下依赖:
<dependencies>
<!-- Bouncy Castle Provider 用于支持 PKCS7Padding -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.72</version> <!-- 请检查最新版本 -->
</dependency>
<!-- Apache Commons Lang 用于字符串操作 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version> <!-- 请检查最新版本 -->
</dependency>
</dependencies>
2. java 工具类AESUtil .java
/**
* AES/CBC/PKCS5Padding 加密解密工具类 (JDK 8 原生支持)
* 注意:密钥和IV需要妥善管理,不建议硬编码在代码中
* PKCS5Padding 和 PKCS7Padding 填充方式一致
*/
public class AESUtil {
// 加密算法、模式、填充方式
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding"; // 使用 JDK 原生支持的 PKCS5Padding
// 默认密钥和初始化向量(IV),建议从外部配置读取
private static final String DEFAULT_KEY = "abcdefghigklmnop"; // 16字节 密钥(必须为16、24或32字节)
private static final String DEFAULT_IV = "abcdefghigklmnop"; // 16字节 初始化向量(必须为16字节)
/**
* AES/CBC/PKCS5Padding 加密
* @param plaintext 待加密的明文
* @return Base64编码的加密字符串
*/
public static String encrypt(String plaintext) {
return encrypt(plaintext, DEFAULT_KEY, DEFAULT_IV);
}
/**
* AES/CBC/PKCS5Padding 加密
* @param plaintext 待加密的明文
* @param keyStr 密钥(必须为16、24或32字节)
* @param ivStr 初始化向量(必须为16字节)
* @return Base64编码的加密字符串
*/
public static String encrypt(String plaintext, String keyStr, String ivStr) {
if (plaintext == null || plaintext.isEmpty()) {
// throw new IllegalArgumentException("Plaintext cannot be null or empty");
return "";
}
try {
// 将密钥和IV转换为字节数组并封装
SecretKeySpec keySpec = new SecretKeySpec(keyStr.getBytes(StandardCharsets.UTF_8), ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(ivStr.getBytes(StandardCharsets.UTF_8));
// 获取并初始化Cipher对象
Cipher cipher = Cipher.getInstance(TRANSFORMATION); // 使用 JDK 原生支持的 PKCS5Padding
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
// 执行加密操作
byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 返回Base64编码的字符串
return Base64.getEncoder().encodeToString(encryptedBytes);
} catch (Exception e) {
throw new RuntimeException("Encryption failed", e);
}
}
/**
* AES/CBC/PKCS5Padding 解密
* @param ciphertext Base64编码的密文
* @return 解密后的明文
*/
public static String decrypt(String ciphertext) {
return decrypt(ciphertext, DEFAULT_KEY, DEFAULT_IV);
}
/**
* AES/CBC/PKCS5Padding 解密
* @param ciphertext Base64编码的密文
* @param keyStr 密钥(必须与加密时使用的密钥一致)
* @param ivStr 初始化向量(必须与加密时使用的IV一致)
* @return 解密后的明文
*/
public static String decrypt(String ciphertext, String keyStr, String ivStr) {
if (ciphertext == null || ciphertext.isEmpty()) {
return "";
// throw new IllegalArgumentException("Ciphertext cannot be null or empty");
}
try {
// 将密钥和IV转换为字节数组并封装
SecretKeySpec keySpec = new SecretKeySpec(keyStr.getBytes(StandardCharsets.UTF_8), ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(ivStr.getBytes(StandardCharsets.UTF_8));
// 获取并初始化Cipher对象
Cipher cipher = Cipher.getInstance(TRANSFORMATION); // 使用 JDK 原生支持的 PKCS5Padding
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// 先将Base64编码的字符串解码
byte[] encryptedBytes = Base64.getDecoder().decode(ciphertext);
// 执行解密操作
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
// 返回解密后的字符串
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("Decryption failed", e);
}
}
/**
* 测试示例
*/
public static void main(String[] args) {
String originalText = "hello AES CBC";
// 加密
String encryptedText = encrypt(originalText);
System.out.println("加密后 (Base64): " + encryptedText);
// 解密
String decryptedText = decrypt(encryptedText);
System.out.println("解密后: " + decryptedText);
// 验证
System.out.println("解密是否成功: " + originalText.equals(decryptedText));
}
}
四、前端 Vue 中使用 AES-CBC-Pkcs7 加密解密
1. 安装 CryptoJS 库
首先,使用 npm 或 yarn 安装 crypto-js
:
npm install crypto-js
# 或
yarn add crypto-js
2. 创建工具文件 AESUtils.js
创建一个单独的 JavaScript 文件(如 cryptoUtils.js)来存放加密解密函数,方便复用:
import CryptoJS from 'crypto-js';
// const CryptoJS = require('crypto-js'); //引用AES源码js
const key = CryptoJS.enc.Utf8.parse("abcdefghigklmnop"); //密钥
const iv = CryptoJS.enc.Utf8.parse("abcdefghigklmnop"); //密钥偏移量
// 加密方法 - AES-CBC-Pkcs7
export function EncryptCBCPkcs7(word) {
const encrypted = CryptoJS.AES.encrypt(word, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
// 解密方法 - AES-CBC-Pkcs7
export function DecryptCBCPkcs7(word) {
const decrypt = CryptoJS.AES.decrypt(word, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypt.toString(CryptoJS.enc.Utf8);
}
3. 测试使用
// 引入js
import { EncryptCBCPkcs7,DecryptCBCPkcs7 } from '@/utils/AESUtils';
//编写测试方法
function test(){
try {
const encrypted = EncryptCBCPkcs7('Hello, World!');
console.log('加密结果:', encrypted);
const decrypted = DecryptCBCPkcs7(encrypted);
console.log('解密结果:', decrypted);
} catch (error) {
console.error('错误:', error);
}
}