3、常见密码SDK介绍
3.1 为什么需要了解多个SDK
说实话,搞密码兼容测试,光知道一个SDK是不够的。你可能在A项目用OpenSSL,到了B项目面对的是Java环境,或者嵌入式设备只能用libsodium。每个SDK的算法实现细节、默认参数、线程安全模型都有差异。
我遇到过几次因为SDK行为不一致导致的线上问题——比如一次TLS握手,在OpenSSL上正常,换到Bouncy Castle就报错。后来查出来是因为默认的填充方式不同。你想想看,如果提前了解这些差异,测试用例就能更有针对性。
3.2 OpenSSL
OpenSSL是C语言写的,业界事实标准。支持TLS/SSL、对称加密、哈希、公钥加密、证书管理,几乎覆盖所有主流算法。我个人习惯在Linux服务器上直接用系统自带的OpenSSL做基准测试。
EVP_DecryptInit_ex这类函数的参数顺序。
代码示例(计算SHA-256):
#include <openssl/evp.h>
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
EVP_DigestUpdate(ctx, data, len);
EVP_DigestFinal_ex(ctx, md, &md_len);
EVP_MD_CTX_free(ctx);
3.3 Bouncy Castle
Bouncy Castle是Java(也有C#版)的密码库,以“算法齐全”出名。很多Java企业项目用JCA/JCE做标准接口,但遇到不支持的算法(比如SM2/SM3/SM4)就会依赖Bouncy Castle。
我建议:做兼容测试时,最好同时拿Bouncy Castle和原生JCE各跑一遍。因为它俩的RSA密钥生成策略不同——Bouncy Castle默认用BC模式,JCE用OpenSSL模式,生成的PKCS#1格式不一致。
Security.addProvider(new BouncyCastleProvider())放在了native provider后面,导致某些算法自动选择了低版本实现。
3.4 Crypto++
Crypto++是C++模板库,性能不错,适合嵌入式或桌面应用。它的设计偏底层——你得自己管理内存、选择模式、处理填充。相比OpenSSL,Crypto++的API更“原始”。
举个例子:AES-CBC加密,OpenSSL帮你处理了IV和填充,但Crypto++需要你手动调用StreamTransformationFilter。兼容测试时特别容易忽略填充方式——Crypto++默认PKCS#7,而某些旧版本OpenSSL默认是zero padding。这不匹配就会导致解密失败。
// Crypto++ AES-CBC加密
CBC_Mode<AES>::Encryption enc;
enc.SetKeyWithIV(key, keySize, iv);
StringSource ss(plain, true,
new StreamTransformationFilter(enc,
new StringSink(cipher)
)
); // 默认PKCS#7填充
3.5 libsodium
libsodium是纯C库,主打“好用、安全、不易误用”。它抽象了复杂操作——比如你不需要自己选nonce,它用随机数自动生成。我特别喜欢它的crypto_secretbox,封装了XSalsa20 + Poly1305,一个函数搞定加密认证。
做兼容测试时,libsodium最大的问题是平台依赖:它用了CPU的特定指令集(比如SSE4.1),在老旧设备上回退到纯软件实现。我曾经在一台ARM Cortex-A7设备上跑测试,发现加解密速度慢了50倍——就是因为没有硬件加速。
// libsodium对称加密
unsigned char cipher[crypto_secretbox_MACBYTES + message_len];
crypto_secretbox_easy(cipher, message, message_len, nonce, key);
// 自动完成加密+认证
3.6 Java JCA/JCE
JCA/JCE是Java平台的标准密码接口,不是具体实现。它背后可以挂任何Provider(比如OpenJDK自带的SunJCE、Bouncy Castle、甚至IBM的JCECB)。
好处是代码移植性强,坏处是不同Provider的行为细节有差异。我在测试ZGC性能时发现,用SunJCE的RSA签名和用Bouncy Castle的同样算法,签名长度相差了几个字节——因为DER编码的填充位不同。
JCE兼容测试的核心:锁定Provider。建议在测试用例里显式指定cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE"),不要只传算法名。
// 指定Provider获取Cipher实例
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
3.7 各SDK核心差异对比
下面这个表格总结了我个人最关注的几个维度,做兼容测试时可以直接参考。
| SDK | 语言 | 主要算法支持 | 许可证 | 典型场景 |
|---|---|---|---|---|
| OpenSSL | C | SSL/TLS、RSA、ECC、AES、SHA-2/3 | OpenSSL (Apache-like) | 服务端、网络通信、证书管理 |
| Bouncy Castle | Java/C# | SM2/3/4、GOST、几乎所有国家算法 | MIT (BC) | Java企业应用、国密场景 |
| Crypto++ | C++ | AES、RSA、ECC、SHA、多线程模式 | Boost (类似BSD) | 嵌入式、桌面客户端、低延迟场景 |
| libsodium | C | XSalsa20、Curve25519、Blake2b、AEGIS | ISC | 移动端、物联网、高安全性项目 |
| JCA/JCE | Java | 取决于Provider(一般AES、RSA、SHA) | Java标准(GPL+CE) | Java后端、中间件、Web服务 |
3.8 如何选择SDK进行兼容测试
这是一个常见的决策流程,我画了一张图帮你梳理。