3. ECC核心算法
讲完椭圆曲线数学基础,咱们得聊聊实际应用了。ECC 在密码学里最核心的两个场景就是密钥交换和数字签名。说白了,一个是为了安全地商量出共同密钥,另一个是为了证明“这东西确实是我发的”。
我个人习惯把 ECDH 和 ECDSA 放在一起讲,因为它们的数学原理同源,但使用方式完全不同。你想想看,一个是对称加密的前置步骤,一个是身份认证的核心手段,搞混了可不行。
3.1 ECDH 密钥交换:怎么在不安全的信道上商量密钥?
ECDH 的全称是 Elliptic Curve Diffie-Hellman。它解决的问题很古老:两个人隔着一条被窃听的网线,怎么安全地约定一个只有他俩知道的秘密?
我记得刚入行时,有人问我:“直接把密钥加密发过去不行吗?” 嗯,问题是你俩一开始就没有共享密钥,拿什么加密?这就是“密钥分发问题”。ECDH 就是答案。
3.1.1 数学原理
ECDH 依赖的是椭圆曲线上的标量乘法,以及“已知公钥和基点,求私钥”的困难性。流程其实很简洁:
- 双方各自生成密钥对:Alice 有私钥
dA,公钥QA = dA * G;Bob 有私钥dB,公钥QB = dB * G。 - 交换公钥:Alice 把
QA发给 Bob,Bob 把QB发给 Alice。即使被窃听也没关系。 - 各自计算共享点:Alice 计算
S = dA * QB,Bob 计算S = dB * QA。 - 结果相同:因为
dA * QB = dA * (dB * G) = dB * (dA * G) = dB * QA,两人得到同一个点S。 - 导出会话密钥:取
S的 x 坐标,经过哈希后作为对称加密的密钥。
核心要点:共享点 S 的 x 坐标就是双方共同秘密。攻击者只有 QA 和 QB,但解不出 dA 或 dB,也就算不出 S。这就是 ECDH 的安全性根基。
3.1.2 我在项目中踩过的坑
我曾经在实现 ECDH 时犯过一个低级错误:没有验证对方公钥是否在曲线上。结果呢?攻击者可以发送一个不在曲线上的点,导致我推导出的共享密钥只有很少的熵,直接被暴力破解。嗯,从那以后,我每次做 ECDH 都会先调用一次 EC_POINT_is_on_curve()。
避坑指南:ECDH 实现必须做三件事——1) 验证公钥在曲线上;2) 验证公钥不是无穷远点;3) 使用安全的随机数生成私钥。少一步都可能出大问题。
3.2 ECDSA 数字签名:如何证明你是你?
ECDSA 是 ECC 在数字签名领域的标准算法。它的作用很简单:签名者用私钥对消息签名,验证者用公钥验证签名是否有效。我经常跟团队说,签名不是加密,它不隐藏消息内容,它只保证完整性和不可否认性。
3.2.1 签名生成流程
ECDSA 签名生成需要以下步骤。注意,这里每一步的顺序都不能乱:
- 计算消息哈希:
e = HASH(message),通常用 SHA-256。 - 生成随机数 k:这是关键!k 必须每次不同且不可预测。
- 计算点 R = k * G:取 R 的 x 坐标作为
r。 - 计算 s = k⁻¹ * (e + r * d) mod n:其中 d 是私钥,n 是曲线的阶。
- 输出签名 (r, s):这就是最终的签名值。
个人经验:随机数 k 绝对不能重复,也不能被预测。2010 年索尼 PS3 的 ECDSA 签名被破解,就是因为索尼用了固定的 k 值。攻击者只需要两个签名就能算出私钥。我每次看到这个案例都觉得后背发凉。
3.2.2 签名验证流程
验证者收到消息和签名 (r, s) 后,做以下计算:
- 计算消息哈希:
e = HASH(message)。 - 计算 w = s⁻¹ mod n。
- 计算 u1 = e * w mod n,u2 = r * w mod n。
- 计算点 P = u1 * G + u2 * Q,其中 Q 是签名者的公钥。
- 验证:如果 P 的 x 坐标等于 r,则签名有效;否则无效。
为什么这样能验证?因为 P = u1*G + u2*Q = (e*w)*G + (r*w)*(d*G) = w*(e + r*d)*G = (k⁻¹)*(e + r*d)*G = k*G = R。数学上完美自洽。
3.3 ECDH 与 ECDSA 的核心区别
很多人刚接触时会把这两个搞混。我整理了一个对比表,你一看就明白:
| 对比维度 | ECDH 密钥交换 | ECDSA 数字签名 |
|---|---|---|
| 核心目的 | 协商共享密钥 | 验证身份和完整性 |
| 参与方 | 双方都需要生成密钥对 | 签名者持有私钥,验证者持有公钥 |
| 是否需要随机数 | 不需要(私钥固定即可) | 每次签名都需要新鲜随机数 k |
| 输出结果 | 共享点 S 的 x 坐标 | 签名对 (r, s) |
| 安全性依赖 | ECDLP 困难性 | ECDLP + 随机数质量 |
| 典型应用 | TLS 握手、SSH 密钥交换 | 代码签名、区块链交易签名 |
3.4 知识体系总览
下面这张图把 ECDH 和 ECDSA 在整个 ECC 应用层的位置梳理清楚了。我建议你把它当作本章的思维导图来记:
3.5 代码示例:OpenSSL 中的 ECDH 与 ECDSA
光讲理论不过瘾,咱们直接看代码。下面是用 OpenSSL 实现 ECDH 密钥交换的核心片段:
// ECDH 密钥交换示例 (使用 prime256v1 曲线)
EC_KEY *alice_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_generate_key(alice_key);
EC_KEY *bob_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_generate_key(bob_key);
// 获取公钥
const EC_POINT *alice_pub = EC_KEY_get0_public_key(alice_key);
const EC_POINT *bob_pub = EC_KEY_get0_public_key(bob_key);
// 计算共享密钥 (Alice 侧)
unsigned char secret[32];
int field_size = EC_GROUP_get_degree(EC_KEY_get0_group(alice_key));
ECDH_compute_key(secret, (field_size + 7) / 8,
bob_pub, alice_key, NULL);
// Bob 侧同理,结果相同
// 注意:实际使用中需要对 secret 做哈希后作为会话密钥
ECDSA 签名和验证的代码也不复杂:
// ECDSA 签名
EC_KEY *sign_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_generate_key(sign_key);
unsigned char msg[] = "Hello, ECDSA!";
unsigned char hash[32];
SHA256(msg, sizeof(msg), hash);
ECDSA_SIG *sig = ECDSA_do_sign(hash, 32, sign_key);
// sig->r 和 sig->s 就是签名值
// ECDSA 验证
int valid = ECDSA_do_verify(hash, 32, sig, sign_key);
// valid == 1 表示签名有效
性能提示:ECDSA 签名验证比签名生成快很多,因为验证时不需要生成随机数。我在做高并发服务时,会把签名验证放在热点路径上,签名生成放在冷路径或异步处理。这样能显著降低延迟。
3.6 本章小结
ECDH 和 ECDSA 是 ECC 应用的两大支柱。ECDH 解决的是“如何安全地商量密钥”,ECDSA 解决的是“如何证明身份和完整性”。它们的数学基础相同,但使用场景和安全要求不同。我个人建议你在实际项目中,优先使用标准库的实现,不要自己造轮子——尤其是随机数生成和曲线参数校验,稍有不慎就是安全漏洞。