1. 概述
AES(Advanced Encryption Standard,高级加密标准)是目前全球应用最广泛的对称加密算法。它于 2001 年由美国 NIST 正式发布,用来替代老化且不再安全的 DES 算法。
与 RSA 不同,AES 加密和解密使用的是同一把密钥(即对称密钥)。它的特点是极高的运算效率和极强的安全性。因为其计算极快、极难破解,被广泛运用于日常的 HTTPS 网页浏览、Wi-Fi 安全认证以及网盘文件加密中
2. AES加密原理
2.1 核心特征与规格
AES 属于分组密码(Block Cipher)。AES 加密时,无论你的原始文件有多大,它都会固定将数据切分成 128位(16字节)的“数据块” 逐个处理。不过,它允许你选择不同长度的密钥:
- AES-128:密钥长度 128 位,加密执行 10 轮 运算。速度最快,适用于大部分日常消费级软件;
- AES-192:密钥长度 192 位,加密执行 12 轮 运算。常用于部分特定企业级安全环境;
- AES-256:密钥长度 256 位,加密执行 14 轮 运算。安全性最高,即常说的“军工级加密”,几乎无法被暴力破解,适用于金融、军事及核心隐私数据;
2.2 AES加密原理(过程)
在一轮加密中,16个字节的输入会被排列成一个 4×4 的矩阵(状态矩阵)。除了第一轮前的“初始轮”和最后一轮略有简化外,AES 的每一轮都在循环做以下 4 个数学变换:
- 1. 字节替代(SubBytes):通过一个固定的置换表(S盒,S-box),将矩阵里的每个字节映射、替换成另一个全新的字节。这一步提供了非线性能力,防止被敌手轻易推导;
- 2. 行移位(ShiftRows):将 4×4 矩阵的后三行按规则分别向左循环移动 1、2、3 个字节,错开数据位置;这一步把每一行的数据打乱打散;
- 3. 列混淆(MixColumns):通过复杂的矩阵乘法运算,把每一列中的 4 个字节重新计算并混合(有限域 GF(28) 矩阵乘法),让单个字节的改动迅速扩散到整个分组。这确保了局部的一点小改动会扩散影响到整体,形成雪崩效应(最后一轮会跳过此步);
- 4. 轮密钥加(AddRoundKey):将当前阶段的矩阵数据,与根据你输入的初始密钥派生出来的“轮密钥”,进行逐位的异或(XOR)计算。这是唯一让密码真正与你的密钥结合的一步;
解密过程:完全是上述步骤的逆向操作(如逆行移位、逆字节替代等),配合相同的密钥即可完美还原明文;
2.3 工作模式
因为 AES 每次只能加密 16 个字节,当面对超长的整段文本时,需要配合工作模式使用。常见的模式有:
- ECB(电子密码本模式):最简单的模式。每一块独立用 AES 加密。缺点是相同的明文块会生成相同的密文块,极不安全,严禁在生产环境使用;
- CBC(密码块链模式):每一块加密前,先与前一个块的密文做异或。需要一个初始化向量(IV)。安全性好,但无法进行并行计算;
- CTR(计数器模式):把加密变成了一个流密码。效率极高,支持多核 CPU 并行加解密;
- GCM(伽罗瓦/计数器模式):🌟 现代首选推荐。 它不仅能加密(保密性),还能在加密的同时生成认证标签(完整性,防篡改)。目前 HTTPS(TLS 1.3)和主流网络隧道默认均首选 AES-GCM;
3. C语言实现AES加密
在 C 语言中,完全手动从头编写包含 S-Box、列混合矩阵乘法的 AES 算法不仅容易引入缓冲区溢出等安全漏洞,而且性能和安全性也无法保障。生产环境强烈建议使用成熟的密码库(如 OpenSSL, Mbed TLS 等),以下分别提供两种最佳实现方式:
- 轻量级开源纯 C 实现(适合嵌入式、极简项目或学习算法原理);
- 基于 OpenSSL 库的 C 实现(推荐,适用于工业级开发);
3.1 C 语言核心代码实现(零依赖)
这里为您提供一个零依赖、纯 C 语言编写的 AES-128-ECB 加密与解密的核心实现。
为了让代码尽可能清晰、易读,本示例精简了复杂的矩阵变换,采用一维数组来直观表达 4×4 矩阵,并严格按照 AES 标准的四个步骤(SubBytes, ShiftRows, MixColumns, AddRoundKey)进行模块化编写:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// AES-128 参数定义
#define AES_BLOCK_SIZE 16
#define AES_ROUNDS 10
#define AES_KEY_SCHEDULE_SIZE 176
// 1. 标准 S 盒 (SubBytes 步骤使用)
static const uint8_t sbox[256] = {
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};
// 2. 逆 S 盒 (解密使用)
static const uint8_t inv_sbox[256] = {
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
};
// 轮常数 Rcon
static const uint8_t Rcon[11] = {
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
};
// 有限域 GF(2^8) 乘法辅助函数:乘以 2
static uint8_t xtime(uint8_t x) {
return (x << 1) ^ (((x >> 7) & 1) ? 0x1b : 0x00);
}
// 轮密钥加步骤
static void AddRoundKey(uint8_t *state, const uint8_t *roundKey) {
for (int i = 0; i < 16; i++) {
state[i] ^= roundKey[i];
}
}
// 字节代替步骤
static void SubBytes(uint8_t *state) {
for (int i = 0; i < 16; i++) state[i] = sbox[state[i]];
}
static void InvSubBytes(uint8_t *state) {
for (int i = 0; i < 16; i++) state[i] = inv_sbox[state[i]];
}
// 行移位步骤 (使用一维数组模拟 4x4 矩阵)
static void ShiftRows(uint8_t *state) {
uint8_t tmp;
// 第 2 行循环左移 1 位
tmp = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = tmp;
// 第 3 行循环左移 2 位
tmp = state[2]; state[2] = state[10]; state[10] = tmp;
tmp = state[6]; state[6] = state[14]; state[14] = tmp;
// 第 4 行循环左移 3 位
tmp = state[3]; state[3] = state[15]; state[15] = state[11]; state[11] = state[7]; state[7] = tmp;
}
static void InvShiftRows(uint8_t *state) {
uint8_t tmp;
// 第 2 行循环右移 1 位
tmp = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = tmp;
// 第 3 行循环右移 2 位
tmp = state[2]; state[2] = state[10]; state[10] = tmp;
tmp = state[6]; state[6] = state[14]; state[14] = tmp;
// 第 4 行循环右移 3 位
tmp = state[7]; state[7] = state[11]; state[11] = state[15]; state[15] = state[3]; state[3] = tmp;
}
// 列混淆步骤
static void MixColumns(uint8_t *state) {
uint8_t Tmp, Tm, t;
for (int i = 0; i < 16; i += 4) {
t = state[i] ^ state[i+1] ^ state[i+2] ^ state[i+3];
Tmp = state[i];
Tm = state[i] ^ state[i+1]; Tm = xtime(Tm); state[i] ^= Tm ^ t;
Tm = state[i+1] ^ state[i+2]; Tm = xtime(Tm); state[i+1] ^= Tm ^ t;
Tm = state[i+2] ^ state[i+3]; Tm = xtime(Tm); state[i+2] ^= Tm ^ t;
Tm = state[i+3] ^ Tmp; Tm = xtime(Tm); state[i+3] ^= Tm ^ t;
}
}
static void InvMixColumns(uint8_t *state) {
uint8_t a, b, c, d;
for (int i = 0; i < 16; i += 4) {
a = state[i]; b = state[i+1]; c = state[i+2]; d = state[i+3];
uint8_t x = xtime(a ^ b ^ c ^ d);
uint8_t y = xtime(xtime(a ^ c ^ x));
uint8_t z = xtime(xtime(b ^ d ^ x));
state[i] ^= y ^ xtime(a ^ b);
state[i+1] ^= z ^ xtime(b ^ c);
state[i+2] ^= y ^ xtime(c ^ d);
state[i+3] ^= z ^ xtime(d ^ a);
}
}
// 密钥扩展算法
void AES_KeyExpansion(const uint8_t *key, uint8_t *roundKeys) {
int i = 0;
// 前 16 字节等于原始密钥
for (i = 0; i < 16; i++) roundKeys[i] = key[i];
while (i < AES_KEY_SCHEDULE_SIZE) {
uint8_t tmp[4];
for (int j = 0; j < 4; j++) tmp[j] = roundKeys[i - 4 + j];
if (i % 16 == 0) {
// RotWord: 循环左移 1 字节
uint8_t k = tmp[0];
tmp[0] = tmp[1]; tmp[1] = tmp[2]; tmp[2] = tmp[3]; tmp[3] = k;
// SubWord: 通过 S 盒替换
tmp[0] = sbox[tmp[0]]; tmp[1] = sbox[tmp[1]];
tmp[2] = sbox[tmp[2]]; tmp[3] = sbox[tmp[3]];
// 异或轮常数 Rcon
tmp[0] ^= Rcon[i / 16];
}
for (int j = 0; j < 4; j++) {
roundKeys[i] = roundKeys[i - 16] ^ tmp[j];
i++;
}
}
}
// 单个 16 字节分组的 AES 加密
void AES_Encrypt_Block(const uint8_t *in, uint8_t *out, const uint8_t *roundKeys) {
uint8_t state[16];
memcpy(state, in, 16);
// 初始轮:轮密钥加
AddRoundKey(state, roundKeys);
// 1 到 9 轮循环
for (int round = 1; round < AES_ROUNDS; round++) {
SubBytes(state);
ShiftRows(state);
MixColumns(state);
AddRoundKey(state, roundKeys + (round * 16));
}
// 第 10 轮(最后一轮无 MixColumns)
SubBytes(state);
ShiftRows(state);
AddRoundKey(state, roundKeys + (AES_ROUNDS * 16));
memcpy(out, state, 16);
}
// 单个 16 字节分组的 AES 解密
void AES_Decrypt_Block(const uint8_t *in, uint8_t *out, const uint8_t *roundKeys) {
uint8_t state[16];
memcpy(state, in, 16);
// 初始轮:反向轮密钥加
AddRoundKey(state, roundKeys + (AES_ROUNDS * 16));
// 9 到 1 轮反向循环
for (int round = AES_ROUNDS - 1; round > 0; round--) {
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(state, roundKeys + (round * 16));
InvMixColumns(state);
}
// 最后一轮
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(state, roundKeys);
memcpy(out, state, 16);
}
// 主函数测试
int main() {
// 128位 密钥 (16字节)
uint8_t key[16] = {
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
};
// 128位 测试明文 (16字节)
uint8_t plaintext[16] = {
0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a
};
uint8_t ciphertext[16];
uint8_t decryptedtext[16];
uint8_t roundKeys[AES_KEY_SCHEDULE_SIZE];
// 1. 扩展密钥
AES_KeyExpansion(key, roundKeys);
// 2. 加密
AES_Encrypt_Block(plaintext, ciphertext, roundKeys);
// 3. 解密
AES_Decrypt_Block(ciphertext, decryptedtext, roundKeys);
// 打印结果
printf("原始明文: ");
for(int i=0; i<16; i++) printf("%02x ", plaintext[i]);
printf("\n");
printf("加密密文: ");
for(int i=0; i<16; i++) printf("%02x ", ciphertext[i]);
printf("\n");
printf("解密结果: ");
for(int i=0; i<16; i++) printf("%02x ", decryptedtext[i]);
printf("\n");
// 验证
if (memcmp(plaintext, decryptedtext, 16) == 0) {
printf("\n解密验证成功!数据一致。\n");
} else {
printf("\n解密失败!数据损坏。\n");
}
return 0;
}
3.2 纯 C 语言轻量级实现(使用开源 tiny-AES-c)
对于没有外部库依赖的小型项目,可以使用业内最广为人知的单文件纯 C 实现库 tiny-AES-c。它极其轻量,只包含 aes.h 和 aes.c 两个文件。
这里采用安全级别最高且支持认证加密的 AES-128-CBC 模式示例:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
// 假设项目引入了 tiny-AES-c 的头文件
#include "aes.h"
void print_hex(const char *title, const uint8_t *buf, size_t len) {
printf("%s: ", title);
for (size_t i = 0; i < len; i++) {
printf("%02x", buf[i]);
}
printf("\n");
}
int main(void) {
// 1. 准备密钥 (AES-128 需要 16 字节 / 128 位)
uint8_t key[16] = {
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
};
// 2. 准备初始化向量 IV (CBC 模式必须,16 字节)
uint8_t iv[16] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};
// 3. 待加密数据(AES 分组大小必须是 16 字节的整数倍,不足需填充)
// "Hello World! 123" 正好 15 字符 + '\0' = 16 字节
uint8_t text[16] = "Hello World! 123";
printf("原始明文: %s\n", text);
// --- 加密过程 ---
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
// 原地加密 (in-place encryption)
AES_CBC_encrypt_buffer(&ctx, text, 16);
print_hex("加密后密文", text, 16);
// --- 解密过程 ---
// 注意:CBC 模式解密前必须重新初始化 IV(因为 IV 在加密过程中会被修改)
uint8_t iv_reset[16] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};
AES_init_ctx_iv(&ctx, key, iv_reset);
// 原地解密
AES_CBC_decrypt_buffer(&ctx, text, 16);
printf("解密后明文: %s\n", text);
return 0;
}
3.3 工业级 OpenSSL 实现(使用 AES-256-GCM 推荐标准)
在实际生产或服务器端开发中,强烈建议使用包含完整硬件加速(如 x86 的 AES-NI 指令集)和防侧信道攻击的 OpenSSL。推荐采用提供数据加解密与完整性校验的 AES-256-GCM 模式:
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
int aes_gcm_encrypt(const unsigned char *plaintext, int plaintext_len,
const unsigned char *key, const unsigned char *iv, int iv_len,
unsigned char *ciphertext, unsigned char *tag) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int len = 0, ciphertext_len = 0;
// 初始化加密上下文,使用 AES-256-GCM
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
// 设置 IV 长度
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL);
// 初始化 Key 和 IV
EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv);
// 提供待加密的明文
EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len);
ciphertext_len = len;
// 完成加密
EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
ciphertext_len += len;
// 获取认证标签 Tag (GCM 特有,用于防篡改)
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
int main(void) {
// 256 位 Key (32 字节), 96 位 IV (12 字节,GCM 推荐)
unsigned char key[32], iv[12], tag[16];
unsigned char plaintext[] = "AES-GCM Secure Data Transmission in C";
unsigned char ciphertext[128];
// 生成随机 Key 和 IV
RAND_bytes(key, sizeof(key));
RAND_bytes(iv, sizeof(iv));
int cipher_len = aes_gcm_encrypt(plaintext, strlen((char*)plaintext),
key, iv, sizeof(iv),
ciphertext, tag);
printf("加密成功!密文长度: %d 字节\n", cipher_len);
return 0;
}
2.3 注意事项
- 1. 内存填充(Padding): 除 CTR/GCM 模式外,CBC/ECB 模式要求明文长度必须是 16 字节的倍数。在 C 语言中需手动实现 PKCS#7 填充,或选择自动支持流式加解密的 GCM/CTR 模式;
- IV(初始向量)随机性: IV 绝对不能重复使用!每次加密数据时,必须调用操作系统提供的随机数生成器(如 Linux 的
/dev/urandom或 OpenSSL 的RAND_bytes)生成新的 IV; - 敏感内存清除: 加解密完成后,存储
key或明文的 C 语言内存区域,应使用memset_s或OPENSSL_cleanse彻底清空,防止被恶意读取; - 切勿手工实现:请不要直接手动实现。因为手写代码容易受到侧信道攻击(Side-Channel Attack);
4. Python实现AES加密
Python 中实现 AES 加密,推荐使用官方和社区最认可的第三方密码学库 cryptography(比旧的 pycryptodome 或 pycrypto 更安全、性能更好且维护积极)。
这里为您提供两个最实用的 Python 代码示例:
- 1. 目前行业推荐首选的 AES-GCM 模式(自带防篡改校验,无需手动补齐 Padding);
- 2. 传统的兼容性最广的经典 AES-CBC 模式(含自动字节填充);
4.1 准备工作
在使用代码前,请先在终端安装官方密码学库:
pip install cryptography
4.2 首选 AES-GCM 模式
AES-GCM(伽罗瓦/计数器模式)是认证加密(AEAD)的一种。它不仅会加密数据,还会生成一个验证标签(Tag),如果数据在传输中被哪怕篡改了一个比特,解密时就会直接报错:
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def aes_gcm_encrypt(plaintext: str, key: bytes) -> tuple[bytes, bytes]:
"""AES-GCM 加密"""
# 将文本转换为字节流
data = plaintext.encode('utf-8')
# GCM 模式推荐使用 12 字节的随机数作为初始化向量 (Nonce/IV)
nonce = os.urandom(12)
# 初始化 AES-GCM 处理器
aesgcm = AESGCM(key)
# 执行加密(返回的密文已自动包含了认证标签 Tag)
ciphertext = aesgcm.encrypt(nonce, data, associated_data=None)
return ciphertext, nonce
def aes_gcm_decrypt(ciphertext: bytes, nonce: bytes, key: bytes) -> str:
"""AES-GCM 解密"""
aesgcm = AESGCM(key)
# 执行解密(如果密文或随机数被篡改,会抛出 InvalidTag 异常)
decrypted_data = aesgcm.decrypt(nonce, ciphertext, associated_data=None)
return decrypted_data.decode('utf-8')
# --- 测试 AES-GCM ---
if __name__ == "__main__":
# 随机生成一个 256 位 (32 字节) 的强密钥 (对应 AES-256)
my_key = AESGCM.generate_key(bit_length=256)
raw_text = "这是一条极度机密的 Python AES 测试消息!"
# 加密
cipher_bytes, my_nonce = aes_gcm_encrypt(raw_text, my_key)
print(f"【GCM 密文(十六进制)】: {cipher_bytes.hex()}")
# 解密
plain_text = aes_gcm_decrypt(cipher_bytes, my_nonce, my_key)
print(f"【GCM 解密成功】: {plain_text}")
4.3 经典 AES-CBC 模式
AES-CBC 是最传统的加密模式。因为 AES 要求明文必须是 16 字节的整数倍,如果你的字符串长度不够,代码中必须使用 PKCS7 标准 自动把数据补齐,解密后再自动把填充剔除:
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
def aes_cbc_encrypt(plaintext: bytes, key: bytes) -> tuple[bytes, bytes]:
# 1. 生成 16 字节 (128-bit) IV
iv = os.urandom(16)
# 2. PKCS7 填充 (AES 块大小固定为 128 bit / 16 byte)
padder = padding.PKCS7(128).padder()
padded_data = padder.update(plaintext) + padder.finalize()
# 3. 初始化 Cipher 并加密
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
return iv, ciphertext
def aes_cbc_decrypt(iv: bytes, ciphertext: bytes, key: bytes) -> bytes:
# 1. 解密
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
decryptor = cipher.decryptor()
padded_data = decryptor.update(ciphertext) + decryptor.finalize()
# 2. 去除 PKCS7 填充
unpadder = padding.PKCS7(128).unpadder()
plaintext = unpadder.update(padded_data) + unpadder.finalize()
return plaintext
# --- 测试代码 ---
if __name__ == "__main__":
key = os.urandom(32) # 256 位密钥
message = "CBC 模式需要配合 PKCS7 填充".encode('utf-8')
iv, ciphertext = aes_cbc_encrypt(message, key)
print(f"CBC 密文 (Hex): {ciphertext.hex()}")
decrypted_msg = aes_cbc_decrypt(iv, ciphertext, key)
print(f"CBC 解密明文: {decrypted_msg.decode('utf-8')}")
4.4 注意事项
- 1. 绝对不要手写矩阵变换:不要在 Python 中用
for循环和列表去手写 AES 的 S盒、行移位等数学逻辑。Python 是解释型语言,手写循环效率极低,且极易留下安全漏洞; - 2. 严禁重用 IV / Nonce:无论是 CBC 的
IV还是 GCM 的Nonce,每次加密都必须用os.urandom()重新生成。如果两条不同的消息用了同一个 IV,密码学家能极其轻松地破解出您的明文; - 3. 如何存储和传输:密文和 IV 都是二进制字节流(bytes)。如果您需要把它们存进数据库或者通过 JSON 发送,通常的做法是使用
base64.b64encode()或者.hex()将它们转换成纯英文字符串; - 4. 密钥派生(PBKDF2): 如果你的“密钥”是用户输入的普通密码短语(如 “MySecret123″),不能直接作为 AES 密钥,必须先使用
PBKDF2等密钥派生函数将其扩展为标准长度的字节串;
1. 一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。