第3章:非对称加密算法基础——RSA、ECC、SM2
大家好,我是这次课程的主讲工程师。聊到非对称加密,很多人第一反应就是“公钥、私钥来回折腾”。其实没那么玄乎。说白了,就是两把钥匙:一把公开,谁都能拿到;一把私藏,只有你自己知道。
我个人在项目里跟这两种算法打了太多交道。有一次在做金融级数据传输时,就因为在RSA和SM2之间选型犹豫了一周,结果被架构师拉去开了个“紧急会议”。嗯,从那以后,我养成了一个习惯:先弄清楚算法底层的数学脾气,再看调用链怎么设计。
今天这一章,咱们就集中火力,把RSA、ECC、SM2这几个核心算法的原理、密钥生成、加解密过程,以及SDK里怎么调,一次性理清。
核心要点:非对称加密的本质是“单向陷门函数”——正向计算简单,反向推导极难。你手里的私钥,就是这个“陷门”。
3.1 RSA:老牌基石,你大概率用过
RSA 的数学基础是大整数因数分解。我给你两个大素数 p 和 q,乘起来得到 n,很简单。但给你 n,让你反推出 p 和 q,那就难了。这就是 RSA 的“单向门”。
我在项目中遇到过一个问题:有人为了“极致性能”,把 RSA 密钥长度降到了 1024 位。结果安全评审直接被驳回了。现在业界底线是 2048 位,建议用到 4096 位才靠谱。
密钥对生成(伪代码示意)
// 1. 选择两个大素数 p, q
// 2. 计算 n = p * q
// 3. 计算 φ(n) = (p-1)*(q-1)
// 4. 选择公钥指数 e (常用 65537)
// 5. 计算私钥 d = e⁻¹ mod φ(n)
// 公钥: (n, e) 私钥: (n, d)
SDK 调用示例(Java 版)
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair pair = generator.generateKeyPair();
PublicKey pubKey = pair.getPublic();
PrivateKey privKey = pair.getPrivate();
// 加密
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encrypted = cipher.doFinal(plaintext);
// 解密
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] decrypted = cipher.doFinal(encrypted);
我曾经踩过的坑:直接用“RSA/ECB/PKCS1Padding”做加密,结果填充方式导致密文长度不可控。后来全部换成 OAEP,安全性提升了一个档次,密文长度也稳定了。
3.2 ECC:短小精悍,现代密码学的宠儿
ECC 的全称是椭圆曲线密码学。它基于椭圆曲线上的离散对数难题。你想想看,同样安全强度下,ECC 的密钥长度只有 RSA 的十分之一左右。
比如:256 位 ECC 密钥 ≈ 3072 位 RSA 密钥。这对于嵌入式设备或者移动端来说,简直是救星。我做过一个 IoT 项目,设备内存只有 128KB,用 RSA 根本跑不动,换成 ECC 的 P-256 曲线,一下子就通了。
| 安全强度 | RSA 密钥长度 | ECC 密钥长度 |
|---|---|---|
| 80 位 | 1024 | 160 |
| 112 位 | 2048 | 224 |
| 128 位 | 3072 | 256 |
| 256 位 | 15360 | 512 |
ECC 在 SDK 中的调用
// 使用 OpenSSL EVP 接口(C 语言风格)
EVP_PKEY *pkey = EVP_PKEY_new();
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1);
EVP_PKEY_keygen(ctx, &pkey);
// 签名
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey);
EVP_DigestSignUpdate(mdctx, data, data_len);
EVP_DigestSignFinal(mdctx, sig, &sig_len);
我个人习惯:在 ECC 选曲线上,首选 NIST P-256 或 Curve25519。不要选很偏的曲线,否则 SDK 兼容性会出问题。
3.3 SM2:国密标准,自主可控
SM2 是我国发布的椭圆曲线公钥密码算法标准。它的数学基础也是 ECC,但参数和曲线是自主定义的。说白了,就是“我们自己定的一套 ECC 标准”。
我记得在信创项目中,客户明确要求必须用 SM2 做数字信封。当时跑了一遍流程,发现 SM2 的密钥生成效率比 RSA 快很多,但是签名结果长度是固定 64 字节(r || s)。这个细节很容易被忽略。
SM2 密钥对生成(GmSSL 示例)
# Python 调用 GmSSL
from gmssl import sm2
# 自动生成密钥对
private_key, public_key = sm2.generate_keypair()
# 也可以直接指定私钥,计算公钥
# 但建议用随机生成,安全系数更高
SM2 签名与验签
# 签名
signer = sm2.CryptSM2(private_key, public_key)
sign = signer.sign(b"hello", random_hex_str())
# 验签
result = signer.verify(sign, b"hello")
print("验签结果:", result) # True
避坑指南:SM2 在加密时,输出的密文格式是 C1||C2||C3。C1 是椭圆曲线点,C2 是密文,C3 是杂凑值。千万别把顺序搞反了,否则对面解密库会报错。我曾经因为这个顺序问题,和合作伙伴对接到凌晨两点……
3.4 三种算法在 SDK 调用链中的对比
| 对比维度 | RSA | ECC | SM2 |
|---|---|---|---|
| 数学基础 | 大整数分解 | 椭圆曲线离散对数 | 椭圆曲线离散对数 |
| 密钥长度(推荐) | 2048~4096 位 | 256 位 | 256 位 |
| 加解密速度 | 较慢 | 快 | 快 |
| 签名速度 | 慢 | 快 | 快 |
| 公钥体积 | 大(256~512 字节) | 小(32~64 字节) | 小(64 字节) |
| 国内合规 | 通用 | 通用 | 国密标准,必须满足 |
| SDK 典型支持 | OpenSSL, Java Crypto, BouncyCastle | OpenSSL, libsodium, BouncyCastle | GmSSL, 国密 PKI 组件 |
3.5 总结:调用链路里怎么选?
其实没有银弹。如果你的场景是 PC 服务端,对性能不敏感,RSA 2048 位依然够用,生态最成熟。如果是移动端或嵌入式系统,我强烈推荐 ECC,尤其是 Curve25519。
而一旦涉及国内政务、金融、医疗等行业,SM2 是硬门槛。这时候别纠结,直接上 SM2 + SM3 + SM4 全家桶。我见过不少项目因为“图方便”先用 RSA 做原型,结果后面合规审计过不了,全部重写——那代价就大了。
一个小建议:在 SDK 设计时,把算法选择抽象成策略模式(Strategy Pattern)。调用方只需要指定算法标识,底层自动路由到对应的实现。这样以后升级算法或切换密钥长度,改一行配置就行,不用动业务代码。