3. 常见对称加密算法:AES、SM4 原理及 SDK 实现
对称加密是数据防泄漏场景中的“老黄牛”。每次数据传输前,先拿密钥把明文锁起来,到了目的地再用同一个密钥打开。我早年做文件加密项目时,就是在这个环节栽过跟头——选错了算法模式,导致整个加密链路形同虚设。今天咱们就聊聊两个最常用的对称加密:AES 和 SM4。原理要懂,SDK 怎么用更要清楚。
3.1 AES 算法原理
AES(Advanced Encryption Standard)是 2001 年 NIST 推出的标准。它是个分组密码,固定把明文切成 128 位(16 字节)一块,然后拿密钥做多轮变换。密钥长度支持 128、192、256 位,对应轮数分别是 10、12、14。我个人的习惯是:能用 256 位绝不用 128 位,虽然慢一点,但安全冗余多。
每一轮干四件事:
- 字节代换(SubBytes)—— 拿 S 盒把每个字节替换成另一个字节,非线性操作。
- 行移位(ShiftRows)—— 把状态矩阵的每一行向左循环移位,增加扩散。
- 列混合(MixColumns)—— 对每一列做矩阵乘法,进一步打散数据。
- 轮密钥加(AddRoundKey)—— 和本轮子密钥做异或。
最后一轮少一个列混合,其他一样。解密就是反向操作,但 S 盒和列混合的逆矩阵都是固定的。
硬避坑:AES 本身只定义了一个分组加密算法,模式(Mode)才是关键。ECB 模式千万不要在生产环境用——同样的明文会得到同样的密文,攻击者一看就知道“哦,这份文档和那份文档是一样的”。我曾经在审计客户代码时发现他们用了 ECB 加密用户登录 ,直接给判了高危漏洞。
实际 SDK 里我建议直接用 AES-256-GCM。GCM 模式自带认证标签,能防篡改,而且用 CTR 模式流式加密,不需要填充。你想想看,防泄漏场景下一旦数据被篡改,认证失败就能立刻报警,比单纯加密管用多了。
3.2 SM4 算法原理
SM4 是咱们国家的商用密码标准(GM/T 0002)。它也属于分组密码,分组长度固定 128 位,密钥长度也是 128 位,一共迭代 32 轮。我最早接触 SM4 是在一个政务云安全项目里,客户明确要求不能用国际算法,只能上国密。那时候我翻了一遍标准文档,发现它的结构和 AES 有点像,但细节完全不同。
SM4 每轮也是四个操作,但顺序和内容不一样:
- 异或轮密钥 —— 先将状态与子密钥异或。
- 非线性变换 τ —— 由 4 个并行的 S 盒组成,每个 S 盒输入 8 位输出 8 位。
- 线性变换 L —— 对 32 位字做循环移位和异或,加密时用 L,解密时用 L 的逆。
- 轮密钥更新 —— 每轮使用的子密钥由固定的密钥扩展算法生成。
说白了,SM4 的核心也是“混淆 + 扩散”。它的 S 盒是特意设计的,与 AES 的 S 盒没有直接代数关系,所以抵抗差分分析和线性分析的能力有保障。而且 128 位密钥、32 轮迭代,安全强度对标 AES-128。
注意轮密钥细节:SM4 的密钥扩展和加密过程都用了同一个非线性变换 τ,所以密钥扩展的计算量和加密差不多。我在实现时踩过一个坑 —— 用了查表法加速加密,但忘记给密钥扩展也做查表,结果密钥扩展比加密还慢。后来改成统一查表才解决问题。
3.3 SDK 实现对比
实际开发中我主要用过 OpenSSL 和 GmSSL 两套 SDK。下面给出关键代码段,你可以感受到两个算法的实现差异。
AES-256-GCM 加密(OpenSSL)
#include <openssl/evp.h>
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv);
// 加密
int outlen;
unsigned char ciphertext[1024];
EVP_EncryptUpdate(ctx, ciphertext, &outlen, plaintext, plaintext_len);
// 生成认证标签
unsigned char tag[16];
EVP_EncryptFinal_ex(ctx, ciphertext + outlen, &outlen);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
EVP_CIPHER_CTX_free(ctx);
关键点:密钥长度 32 字节(256 位),IV 长度建议 12 字节。标签一定要存储或传输过去,解密时需要验证。
SM4-GCM 加密(GmSSL)
#include <gmssl/sm4.h>
SM4_GCM_CTX *ctx = SM4_GCM_CTX_new();
SM4_GCM_EncryptInit(ctx, key, 16, iv, 12);
// 加密
uint8_t ciphertext[1024];
size_t outlen;
SM4_GCM_EncryptUpdate(ctx, ciphertext, &outlen, plaintext, plaintext_len);
// 获取标签
uint8_t tag[16];
SM4_GCM_EncryptFinal(ctx, ciphertext + outlen, &outlen);
SM4_GCM_GetTag(ctx, tag, 16);
SM4_GCM_CTX_free(ctx);
接口设计的思路几乎一模一样。如果你熟悉 OpenSSL 的 EVP 接口,切换到 GmSSL 只需要换个函数名就好。嗯,这里要注意:GmSSL 的 SM4-GCM 实现要求 IV 长度固定为 12 字节,不能像 AES 那样可以变长,我在测试时因为传了 16 字节 IV 导致初始化失败,排查了半小时。
3.4 性能与安全性对比
| 对比项 | AES-256-GCM | SM4-GCM |
|---|---|---|
| 分组长度 | 128 bit | 128 bit |
| 密钥长度 | 128 / 192 / 256 bit | 128 bit |
| 轮数 | 10 / 12 / 14 | 32 |
| 硬件加速 | AES-NI 指令集,性能极高 | 部分国产 CPU 有 SM4 硬件指令 |
| 国密合规 | 否 | 是(GM/T 0002) |
| 国际兼容性 | 极广,几乎所有平台都支持 | 需专用库或国产平台 |
| 安全强度 | 256 位密钥等效 128 位安全级 | 128 位密钥等效 128 位安全级 |
一句话总结:性能上 AES-256 在有硬件加速时碾压 SM4,但 SM4 在国密合规场景下不可替代。
3.5 选择建议
回到数据防泄漏的实际情况。我参与过的几个项目,选型逻辑很清晰:
- 通用场景:直接上 AES-256-GCM。OpenSSL 几乎每台服务器都装,库稳定,周边工具多。
- 政府、金融、关键基础设施:必须用 SM4-GCM。即使性能差点,也要满足《密码法》要求。
- 混合场景:同时支持两种算法,让客户端或配置文件决定。我以前在 SDK 里做了一组抽象接口,上层只用“加密/解密”,底层根据配置文件切换算法,这样改起来非常省事。
最后提醒一句:不管选 AES 还是 SM4,密钥管理才是安全的短板。千万别把密钥硬编码在代码里,也别用同一个密钥加密所有数据。这是我吃过亏才养成的习惯。