常见加密算法接口:AES加解密接口设计与使用
谈到对称加密,AES 几乎是所有 SDK 里绕不开的基础设施。它不像 RSA 那样慢,也不像 DES 那样脆弱。
我个人习惯把 AES 接口分成两大块:核心加密逻辑,以及上层易用性封装。今天我们就从接口设计者的视角,把这两块拆开揉碎讲清楚。
1. AES 核心概念:你别只会调库
很多同学在项目里只用过一句 openssl_encrypt(),但接口出了问题就一头雾水。
说白了,AES 接口设计落地的关键就三个要素:密钥长度、工作模式、填充方式。
| 要素 | 常见取值 | 我踩过的坑 |
|---|---|---|
| 密钥长度 | AES-128 / AES-256 | 密钥长度错了,解密直接崩 |
| 工作模式 | ECB / CBC / GCM | ECB 不安全,CBC 需 IV,GCM 最推荐 |
| 填充方式 | PKCS7 / NoPadding | 数据长度不对齐,解密出来乱码 |
⚠️ 避坑指南:我曾经在设计中忽略了 IV 的随机性,结果在多租户场景下,两个用户使用相同密钥加密了相似的数据……嗯,你知道那种后果。
2. 接口设计:我是怎么拆的
一个成熟的 AES 接口,绝不是把 openssl_encrypt 的入参直接暴露给用户。那样太混乱了。
我建议至少拆成三层:
- 底层引擎层:只负责加密/解密,暴露
encrypt(key, iv, data)和decrypt(key, iv, ciphertext)。 - 策略层:管理工作模式和填充方式。你想想看,CBC 需要 IV,GCM 需要 tag,ECB 不需要额外参数——该对外还是对内?
- 用户层:只传字符串或字节流,隐藏一切密码学细节。
举个例子。这是我早期写的一个 Go 版本接口核心片段:
type AESCipher struct {
key []byte
mode CipherMode
}
func (c *AESCipher) Encrypt(plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(c.key)
if err != nil {
return nil, err
}
// 带上随机 IV
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
// 内部 PKCS7 padding
plaintext = pkcs7.Pad(plaintext, aes.BlockSize)
ciphertext := make([]byte, len(plaintext))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, plaintext)
// IV + 密文一块返回
return append(iv, ciphertext...), nil
}
注意一个细节:IV 必须随机生成,且每次加密不同。有些同学图省事用固定 IV…… 解密没问题,但相同密钥加密相同明文时,密文会一模一样——这是安全漏洞。
3. 填充方式:为什么 PKCS7 是默认选择
AES 是块加密,块大小固定 16 字节。如果数据不是 16 的倍数,就要填充。
我见过几种填充方式:
- PKCS7:填充多少字节,每个字节就填对应数值。比如缺 5 字节,就填 0x05 重复 5 次。解密后根据最后一个字节去掉尾部。简单可靠。
- NoPadding:要求数据长度恰好是 16 的倍数,否则报错。适合固定长度消息。
- ZeroPadding:填 0x00。但问题在于数据本身可能末尾就有 0,解密后无法区分。别用。
💡 小技巧:我在封装 SDK 时,默认固定位 PKCS7 并内置处理。用户不用感知填充的存在。如果你暴露了 NoPadding 选项,记得做严格校验。
4. 实战 SVGs:AES-SDK 完整流程
下面这张图,是我设计 SDK 时画的工作流,你可以直接参考。
5. 避坑清单:我亲身经历过的 5 个坑
你以为 AES 接口很简单?那是对着文档调库。一旦要做 SDK,全是细节。
下面是我回顾自己开发经历时,必须写进接口测试的案例:
- 密钥长度校验不严格:用户传了个 17 字节的密钥,库没报错,但加解密不稳定。后来我强制校验 key 长度必须为 16/24/32。
- IV 不返回:CBC 模式下加密时不把 IV 拼到密文里,解密端根本没法还原。我曾经设计过单独传 IV,后来全改成 IV + 密文整体返回。
- 多线程并发不安全:如果你把 cipher 对象做成单例但没加锁,加密时数据会被相互覆盖。嗯,线上事故就是这么来的。
- GCM 的 nonce 长度:GCM 模式 nonce 推荐 12 字节,有的库却默认为 16 字节。跨语言互调时,这是大坑。
- 跨语言 PKCS7 不一致:Java 的 PKCS5Padding 与 PKCS7 在 AES 场景下等价,但 Python 直接就叫 PKCS7。别在文档里乱改名。
核心原则:好的 AES 接口,让调用方既不需要知道填充,也不需要关心 IV,更不用手动处理 nonce。接口越“无脑”,用户出错概率越低。
6. 接口签名:我推荐这么设计
最后分享我目前在用的接口签名模式,供你参考:
| 函数 | 签名 | 说明 |
|---|---|---|
| Encrypt | Encrypt(plain []byte) (cipher []byte, err error) |
自动生成 IV,IV 拼在密文头部 |
| Decrypt | Decrypt(cipher []byte) (plain []byte, err error) |
自动从密文前 16 字节解析 IV |
| SetKey | SetKey(key []byte) error |
校验密钥长度,只允许 16/24/32 |
说白了,你记住一句话:把最容易出错的环节,全部封装在 SDK 内部。
我现在做任何加密接口设计,都坚持这个原则。用户只需传入明文和密钥,其他由 SDK 承担。