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,密钥管理才是安全的短板。千万别把密钥硬编码在代码里,也别用同一个密钥加密所有数据。这是我吃过亏才养成的习惯。

对称加密流程对比:AES vs SM4 AES 加密流程 1. 明文分组 (128 bit) 2. 轮密钥加 (AddRoundKey) 3. 字节代换 → 行移位 → 列混合 4. 重复 9/11/13 轮(最后一轮无列混合) 5. 输出密文 (128 bit) SM4 加密流程 1. 明文分组 (128 bit) 2. 异或轮密钥 3. 非线性变换τ (S盒) → 线性变换L 4. 重复 31 轮(共 32 轮) 5. 输出密文 (128 bit) AES 使用 4 个不同操作,每轮结构一致 SM4 每轮仅 τ+L 组合,结构更规整