4. 常见加密模式:AES-GCM、RSA-OAEP、国密SM4
加密模式选对了,项目成功一半。选错了,后面全是坑。
我见过不少团队,上线前才发现加密方式不满足合规要求,或者性能根本扛不住。今天就聊聊三种最常见的模式——AES-GCM、RSA-OAEP、国密SM4——它们各自的适用场景、使用姿势和常见陷阱。
4.1 AES-GCM:对称加密的首选
先问个问题:对称加密里,CBC和GCM,你选哪个?
我个人习惯直接用GCM。为什么?因为它自带认证能力。
CBC模式只能保证机密性,没法防篡改。攻击者改一个密文块,你解密后得到的就是乱码,而且你不知道数据被动过。GCM不同,它会在加密的同时生成一个认证标签(Auth Tag),解密时校验这个标签。说白了就是既加密又防篡改。
GCM的核心特点
- 认证加密(AEAD):加密+完整性校验一次完成
- 支持附加数据(AAD):只校验不解密的头部信息
- 并行计算:硬件加速友好,性能优秀
- IV/Nonce要求:不能重复使用
⚠ 重要:IV/Nonce不能重复
我曾经在压测环境里踩过这个坑。GCM模式下,如果两次加密用了相同的Key和IV/Nonce,攻击者可以恢复出你的密钥。这不是理论风险,是真实可被利用的。
建议:每次加密都生成一个新的12字节随机IV,并和密文一起存储。
来看看代码怎么写。我用Java的Cipher实现举个例子:
// AES-GCM 加密示例(Java)
public static byte[] encryptAesGcm(byte[] plaintext, byte[] key, byte[] aad) throws Exception {
// 生成12字节随机IV
byte[] iv = new byte[12];
SecureRandom.getInstanceStrong().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv); // 128位认证标签
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), spec);
// 设置附加数据(可选)
if (aad != null && aad.length > 0) {
cipher.updateAAD(aad);
}
byte[] ciphertext = cipher.doFinal(plaintext);
// 返回 IV + 密文,方便解密时提取
return ByteBuffer.allocate(iv.length + ciphertext.length)
.put(iv)
.put(ciphertext)
.array();
}
解密时先提取前12字节作为IV,剩下的就是密文(含认证标签)。注意:标签长度推荐128位,不要为了省几个字节改用64位。
AAD(附加数据)是个好东西。我建议把用户ID、版本号等元信息放进去,这样如果有人篡改这些字段,解密会直接失败。
4.2 RSA-OAEP:非对称加密的现代方案
RSA直接加密?千万别这么干。
教科书式的RSA(即RSAES-PKCS1-v1_5)有已知的padding oracle攻击风险。而RSA-OAEP(Optimal Asymmetric Encryption Padding)是经过验证的现代方案。你想想看,如果RSA本身就是个确定性算法,相同的明文加密出相同的密文,这多危险——攻击者可以搞字典攻击。加padding就是给它注入随机性。
| 特性 | RSA-OAEP | RSA-PKCS1-v1_5 |
|---|---|---|
| 安全性 | CCA安全(选择密文攻击安全) | 存在已知漏洞 |
| 随机性 | 每次加密结果不同 | 确定性加密 |
| 填充开销 | 较大(约42字节+摘要长度) | 较小(约11字节) |
| 推荐程度 | 强烈推荐 | ❌ 不建议使用 |
💡 我建议:新项目一律用RSA-OAEP。如果是改造老系统,也尽快迁移。我知道切换加密方案很麻烦,但安全债迟早要还。
使用RSA-OAEP时,有几个参数要注意:
- 摘要算法:推荐SHA-256,别用SHA-1了
- MGF1:掩码生成函数,同样用SHA-256
- 标签(Label):可以传一个可选字符串,比如应用标识,增加安全性
// RSA-OAEP 加密示例(Java)
public static byte[] encryptRsaOaep(byte[] plaintext, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plaintext);
}
注意:RSA有明文长度限制。比如2048位密钥,OAEP填充后最多只能加密190字节(2048/8 - 2*32 - 2)。如果字段超过这个长度,需要用混合加密方案。
混合加密怎么搞?我通常的做法:用RSA加密一个随机的AES密钥,然后用这个AES密钥去加密实际数据。既解决长度问题,又利用对称加密的高性能。
4.3 国密SM4:合规场景下的对称加密
说句实话,SM4在算法层面上和AES没什么本质区别,都是分组密码、128位密钥。但因为合规要求,很多国内项目必须用它。
嗯,这里要注意:SM4本身只是一个分组加密算法,和AES一样,它也有各种工作模式:ECB、CBC、GCM、CTR等。很多SDK默认只暴露ECB模式,千万别直接用——ECB模式相同的明文会得到相同的密文,结构信息完全暴露。
SM4模式选择建议
- 推荐模式:SM4-GCM(认证加密,和AES-GCM完全一样)
- 次选模式:SM4-CBC + HMAC
- 不推荐:SM4-ECB、SM4-CBC不搭配HMAC
不过要注意:不是所有国密SDK都实现了GCM模式。如果SDK不支持,你可以用SM4-CBC,但一定要加上HMAC做完整性校验。我在一个金融项目里就遇到过这种情况,SDK只暴露了CBC模式,客户非要国密。最后我搞了个SM4-CBC + HMAC-SHA256的组合方案,既能满足合规,又能保证完整性。
来看个用Bouncy Castle实现SM4-GCM的例子:
// SM4-GCM 加密示例(Java,使用BouncyCastle)
Security.addProvider(new BouncyCastleProvider());
public static byte[] encryptSm4Gcm(byte[] plaintext, byte[] key) throws Exception {
byte[] iv = new byte[12];
SecureRandom.getInstanceStrong().nextBytes(iv);
Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "SM4"), spec);
byte[] ciphertext = cipher.doFinal(plaintext);
return ByteBuffer.allocate(iv.length + ciphertext.length)
.put(iv)
.put(ciphertext)
.array();
}
有没有发现和AES-GCM的代码几乎一样?对,就是加解密算法换了个名字,其他套路完全一样。这也是我为什么说“搞懂了AES,SM4就上手了”。
⚠ 密钥长度陷阱
SM4密钥固定128位(16字节)。AES支持128/192/256位。有时候团队把AES的256位密钥直接套到SM4上,肯定会报错。我记得有一个项目上线前一天发现这个问题,所有人熬夜重新生成密钥。这种低级错误,越早发现越好。
4.4 三种模式的核心区别对比
| 维度 | AES-GCM | RSA-OAEP | SM4(国密) |
|---|---|---|---|
| 加密类型 | 对称加密 | 非对称加密 | 对称加密 |
| 密钥长度 | 128/192/256位 | 2048/4096位 | 128位 |
| 性能 | 极快(硬件加速) | 慢(非对称运算) | 快(国产芯片加速) |
| 数据长度限制 | 无实际限制 | 最大190字节(2048位密钥) | 无实际限制 |
| 认证能力 | 内置(GCM模式) | 无(需额外签名) | 内置(GCM模式) |
| 合规场景 | 非国密敏感 | 通用场景 | 国内合规必选 |
4.5 避坑总结
说了这么多,总结几个我踩过或见过别人踩过的坑:
- IV/Nonce重复使用:GCM模式下这是死罪。我曾经在压测环境里踩过这个坑,两个线程用了同一个IV,直接破功。
- 密钥硬编码:源码里写死密钥,上生产就忘改。我接手过一个老旧系统,SM4密钥写死在代码里,跑了三年没换过。这等于把门锁的钥匙挂在门上。
- 误用ECB模式:我就问一句,ECB模式下,加密后的数据能看出明文规律吗?能。所以别用ECB。
- SM4的GCM支持:不是所有国密SDK都实现了GCM模式。建议你提前确认SDK文档,免得拿到手才发现不支持。
- 混合加密组合不当:AES密钥用RSA加密时,一定要加padding。我见过直接用RSA/ECB/PKCS1Padding加密AES密钥的,安全性大打折扣。正确做法是RSA-OAEP。
💡 选型建议
- 没有特殊要求:直接上AES-GCM,成熟、高效、安全
- 合规要求(金融、政务):国密SM4-GCM
- 密钥分发、小数据加密:RSA-OAEP
- 大数据量机密:混合加密 AES-GCM + RSA-OAEP