3. ECC核心算法

讲完椭圆曲线数学基础,咱们得聊聊实际应用了。ECC 在密码学里最核心的两个场景就是密钥交换和数字签名。说白了,一个是为了安全地商量出共同密钥,另一个是为了证明“这东西确实是我发的”。

我个人习惯把 ECDH 和 ECDSA 放在一起讲,因为它们的数学原理同源,但使用方式完全不同。你想想看,一个是对称加密的前置步骤,一个是身份认证的核心手段,搞混了可不行。

3.1 ECDH 密钥交换:怎么在不安全的信道上商量密钥?

ECDH 的全称是 Elliptic Curve Diffie-Hellman。它解决的问题很古老:两个人隔着一条被窃听的网线,怎么安全地约定一个只有他俩知道的秘密?

我记得刚入行时,有人问我:“直接把密钥加密发过去不行吗?” 嗯,问题是你俩一开始就没有共享密钥,拿什么加密?这就是“密钥分发问题”。ECDH 就是答案。

3.1.1 数学原理

ECDH 依赖的是椭圆曲线上的标量乘法,以及“已知公钥和基点,求私钥”的困难性。流程其实很简洁:

  1. 双方各自生成密钥对:Alice 有私钥 dA,公钥 QA = dA * G;Bob 有私钥 dB,公钥 QB = dB * G。
  2. 交换公钥:Alice 把 QA 发给 Bob,Bob 把 QB 发给 Alice。即使被窃听也没关系。
  3. 各自计算共享点:Alice 计算 S = dA * QB,Bob 计算 S = dB * QA。
  4. 结果相同:因为 dA * QB = dA * (dB * G) = dB * (dA * G) = dB * QA,两人得到同一个点 S。
  5. 导出会话密钥:取 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 签名生成需要以下步骤。注意,这里每一步的顺序都不能乱:

  1. 计算消息哈希:e = HASH(message),通常用 SHA-256。
  2. 生成随机数 k:这是关键!k 必须每次不同且不可预测。
  3. 计算点 R = k * G:取 R 的 x 坐标作为 r。
  4. 计算 s = k⁻¹ * (e + r * d) mod n:其中 d 是私钥,n 是曲线的阶。
  5. 输出签名 (r, s):这就是最终的签名值。

个人经验:随机数 k 绝对不能重复,也不能被预测。2010 年索尼 PS3 的 ECDSA 签名被破解,就是因为索尼用了固定的 k 值。攻击者只需要两个签名就能算出私钥。我每次看到这个案例都觉得后背发凉。

3.2.2 签名验证流程

验证者收到消息和签名 (r, s) 后,做以下计算:

  1. 计算消息哈希:e = HASH(message)。
  2. 计算 w = s⁻¹ mod n。
  3. 计算 u1 = e * w mod n,u2 = r * w mod n。
  4. 计算点 P = u1 * G + u2 * Q,其中 Q 是签名者的公钥。
  5. 验证:如果 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 应用层的位置梳理清楚了。我建议你把它当作本章的思维导图来记:

ECC 核心算法体系 椭圆曲线密码学 (ECC) ECDH 密钥交换 ECDSA 数字签名 1. 双方生成密钥对 (d, Q) 2. 交换公钥 QA ↔ QB 1. 签名: (r, s) = f(message, d) 2. 验证: 检查 P.x == r 输出: 共享密钥 (x 坐标) 输出: 签名对 (r, s) ⚠ 共同安全基础: 椭圆曲线离散对数问题 (ECDLP) ECDH 不需要随机数 | ECDSA 每次签名必须用新鲜随机数 k

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 解决的是“如何证明身份和完整性”。它们的数学基础相同,但使用场景和安全要求不同。我个人建议你在实际项目中,优先使用标准库的实现,不要自己造轮子——尤其是随机数生成和曲线参数校验,稍有不慎就是安全漏洞。