如果把网络通信比作 “两个人传秘密纸条”,对称加密(比如 AES)就是 “用一把锁把纸条锁起来”—— 但问题来了:怎么把钥匙安全地交给对方? 直接递钥匙会被中途偷走,藏起来又怕对方找不到。这就是 “密钥交换难题”,而 ECDH(椭圆曲线 Diffie-Hellman)就是解决这个难题的 “智能钥匙系统”。
一、先搞懂:ECDH 到底解决了什么问题?
在 ECDH 出现前,人们用传统的 DH(Diffie-Hellman)算法做密钥交换,但 DH 有个明显缺点:密钥太长—— 要达到 “抗破解” 的安全级别,DH 需要 1024 位甚至 2048 位的密钥,这对手机、物联网设备等 “资源小能手” 极不友好(计算慢、耗电多)。
ECDH 则是 “DH 的升级版”,它基于椭圆曲线密码学(ECC) ,核心优势是:
- 同样安全级别下,密钥长度更短(比如 256 位 ECDH ≈ 3072 位 DH);
- 计算速度更快,适合资源受限场景(手机、嵌入式设备);
- 本质还是 “双方无需传递密钥,却能算出同一个共享密钥”。
举个通俗的例子:
- 小明和小红约定好 “用小区花园里的樱花树作为基准(椭圆曲线参数)”;
- 小明偷偷摘了一片樱花叶(自己的私钥),然后把 “用樱花叶蘸颜料印的图案(自己的公钥)” 贴在公告栏;
- 小红偷偷摘了一片枫叶(自己的私钥),也把 “用枫叶蘸颜料印的图案(自己的公钥)” 贴在公告栏;
- 小明用 “自己的樱花叶 + 小红的枫叶图案”,调出了一种紫色颜料(共享密钥);
- 小红用 “自己的枫叶 + 小明的樱花叶图案”,也调出了同一种紫色颜料;
- 之后两人就用 “紫色颜料” 在纸条上做标记(对称加密),外人就算看到公告栏的图案,没有樱花叶 / 枫叶,也调不出紫色颜料。
二、技术原理:ECDH 的 “四步密钥交换法”
ECDH 的核心是椭圆曲线上的 “点加法” —— 不用深究复杂的数学公式,只需记住:椭圆曲线上的点满足 “P + Q = R” 的特殊规则,且 “k*P”(k 个 P 点相加)是 “容易算但难逆推” 的(这就是 “椭圆曲线离散对数问题”,破解难度极高)。
完整的 ECDH 密钥交换过程分 4 步,所有步骤都基于 “双方提前约定的椭圆曲线参数”(比如常用的secp256r1、secp256k1):
步骤 1:约定公共参数
双方(比如 A 和 B)先统一一套椭圆曲线参数,记为(E, G, n):
E:椭圆曲线方程(比如y² = x³ + ax + b);G:曲线上的 “基点”(一个固定的公开点);n:基点G的 “阶”(即n*G = O,O是曲线的无穷远点,类似加法中的 0)。
步骤 2:各自生成密钥对
A 和 B 分别生成自己的 “私钥 - 公钥对”,私钥是秘密的,公钥是公开的:
- A 的操作:
- 随机选一个私钥
d_A(满足1 ≤ d_A ≤ n-1,必须是随机且保密的); - 计算公钥
Q_A = d_A * G(用私钥乘以基点 G,得到曲线上的一个公开点);
- 随机选一个私钥
- B 的操作:
- 随机选一个私钥
d_B(同样满足1 ≤ d_B ≤ n-1,保密); - 计算公钥
Q_B = d_B * G(公开点)。
- 随机选一个私钥
步骤 3:交换公钥
A 把自己的公钥Q_A发给 B,B 把自己的公钥Q_B发给 A。
⚠️ 注意:公钥是公开的,就算被第三方监听也没关系 —— 因为没有私钥,无法逆推出对方的私钥。
步骤 4:计算共享密钥
A 和 B 用 “自己的私钥 + 对方的公钥”,能算出同一个共享密钥:
- A 的计算:
K = d_A * Q_B(用自己的私钥d_A乘以 B 的公钥Q_B); - B 的计算:
K = d_B * Q_A(用自己的私钥d_B乘以 A 的公钥Q_A);
为什么两者相等?因为椭圆曲线的 “点乘法” 满足结合律:
d_A * Q_B = d_A * (d_B * G) = (d_A * d_B) * G = d_B * (d_A * G) = d_B * Q_A。
最后,双方会从K(曲线上的点)中提取 “x 坐标”(或 x+y 坐标),再经过哈希(比如 SHA-256)或 KDF(密钥派生函数)处理,得到最终的 “共享密钥”—— 这个密钥就可以用来做对称加密(比如 AES)了。
三、源码实战:openHiTLS 的 ECDH 实现
openHiTLS 是业界首款面向全场景的开源密码套件,其crypto/ecdh/src/ecdh.c文件实现了 ECDH 的核心逻辑。下面我们结合关键源码,拆解 “密钥对生成” 和 “共享密钥计算” 这两个核心操作。
先了解:openHiTLS 的 ECDH 核心结构体
在分析代码前,先认识几个关键结构体(类似 “装数据的盒子”):
c
运行
// 椭圆曲线组(存储曲线参数E、G、n等)
typedef struct {
int curve_type; // 曲线类型(比如SECP256R1)
bignum_t order; // 基点G的阶n
ec_point_t g; // 基点G
// 其他参数(如曲线方程系数a、b等)
} ec_group_t;
// ECDH上下文(存储密钥对、曲线参数等)
typedef struct {
ec_group_t *group; // 关联的椭圆曲线组
bignum_t priv_key; // 本地私钥d_A/d_B
ec_point_t pub_key; // 本地公钥Q_A/Q_B
int is_initialized; // 上下文是否初始化
} ecdh_ctx_t;
bignum_t:大数类型(处理椭圆曲线运算中的大整数,避免溢出);ec_point_t:椭圆曲线上的点(存储 x、y 坐标)。
1. 生成 ECDH 密钥对:ecdh_generate_key
功能:根据约定的椭圆曲线,生成 “私钥 - 公钥对”,对应原理中的 “步骤 2”。
关键源码片段(来自ecdh.c):
c
运行
/**
* @brief 生成ECDH密钥对
* @param ctx [in/out] ECDH上下文(输出私钥和公钥)
* @return 0成功,非0失败
*/
int ecdh_generate_key(ecdh_ctx_t *ctx) {
// 1. 校验参数(上下文必须初始化,曲线组必须存在)
if (ctx == NULL || !ctx->is_initialized || ctx->group == NULL) {
return ECDH_ERR_INVALID_PARAM; // 无效参数错误
}
ec_group_t *group = ctx->group;
bignum_t *priv_key = &ctx->priv_key;
ec_point_t *pub_key = &ctx->pub_key;
// 2. 生成私钥d(随机数,范围:1 ≤ d ≤ group->order - 1)
// 为什么是这个范围?因为n是G的阶,d必须在[1, n-1]才能保证Q=d*G有效
int ret = bignum_rand_range(priv_key, &group->order);
if (ret != 0) {
return ECDH_ERR_GEN_PRIV_KEY; // 生成私钥失败
}
// 3. 计算公钥Q = d * G(私钥乘以基点)
// ec_point_mul:椭圆曲线点乘法,参数:(曲线组, 输出点, 私钥, 输入点, 临时缓存)
ret = ec_point_mul(group, pub_key, priv_key, &group->g, NULL);
if (ret != 0) {
return ECDH_ERR_GEN_PUB_KEY; // 生成公钥失败
}
return 0; // 密钥对生成成功
}
代码逻辑解读:
- 第一步 “参数校验”:避免空指针或未初始化的上下文,是密码学代码的 “安全习惯”;
- 第二步 “生成私钥”:用
bignum_rand_range生成 “1 到阶 n-1” 的随机数 —— 如果私钥是 0 或 n,会导致公钥是 “无穷远点 O”,无法使用; - 第三步 “计算公钥”:调用
ec_point_mul(点乘法),实现 “d*G” 的运算,得到公钥 Q。
2. 计算共享密钥:ecdh_compute_shared_key
功能:用 “本地私钥 + 对方公钥” 计算共享密钥,对应原理中的 “步骤 4”。
关键源码片段(来自ecdh.c):
c
运行
/**
* @brief 计算ECDH共享密钥
* @param ctx [in] ECDH上下文(含本地私钥)
* @param peer_pub [in] 对方公钥(Q_B/Q_A)
* @param shared_key [out] 输出共享密钥(原始x坐标)
* @param shared_key_len [in/out] 共享密钥长度(输入最大长度,输出实际长度)
* @return 0成功,非0失败
*/
int ecdh_compute_shared_key(const ecdh_ctx_t *ctx, const ec_point_t *peer_pub,
unsigned char *shared_key, size_t *shared_key_len) {
// 1. 校验参数(上下文、对方公钥、输出缓冲区必须有效)
if (ctx == NULL || !ctx->is_initialized || peer_pub == NULL ||
shared_key == NULL || shared_key_len == NULL) {
return ECDH_ERR_INVALID_PARAM;
}
ec_group_t *group = ctx->group;
bignum_t shared_x; // 共享点的x坐标(作为原始共享密钥)
ec_point_t shared_point; // 共享点K = d * peer_pub
// 2. 初始化临时变量(点和大数)
ec_point_init(group, &shared_point);
bignum_init(&shared_x);
// 3. 计算共享点K = 本地私钥 * 对方公钥
int ret = ec_point_mul(group, &shared_point, &ctx->priv_key, peer_pub, NULL);
if (ret != 0) {
ret = ECDH_ERR_COMPUTE_SHARED;
goto cleanup; // 计算失败,跳转到资源释放
}
// 4. 提取共享点的x坐标(标准规定:共享密钥取x坐标,长度固定为曲线的密钥位长)
ret = ec_point_get_affine_x(group, &shared_x, &shared_point, NULL);
if (ret != 0) {
ret = ECDH_ERR_EXTRACT_X;
goto cleanup;
}
// 5. 将x坐标(大数)转为字节流(输出到shared_key)
ret = bignum_to_bytes(&shared_x, shared_key, *shared_key_len, shared_key_len);
if (ret != 0) {
ret = ECDH_ERR_BN_TO_BYTES;
goto cleanup;
}
cleanup:
// 6. 释放临时资源(避免内存泄漏)
ec_point_clear(&shared_point);
bignum_clear(&shared_x);
return ret;
}
代码逻辑解读:
- 第三步 “计算共享点”:核心是
ec_point_mul,实现 “本地私钥 * 对方公钥”,对应原理中的K = d_A*Q_B或K = d_B*Q_A; - 第四步 “提取 x 坐标”:为什么取 x 坐标?因为椭圆曲线的点由 (x,y) 组成,但 x 坐标已足够唯一,且长度固定(比如 256 位曲线的 x 坐标是 32 字节),适合作为共享密钥的原始数据;
- 第五步 “大数转字节流”:
bignum_t是内存中的大数格式,需要转为字节流(比如 32 字节数组),才能给 AES 等对称加密算法使用。
3. 实际使用流程:完整调用示例
结合上面两个核心函数,一个完整的 ECDH 密钥交换流程(A 的视角)如下:
c
运行
void ecdh_demo() {
ecdh_ctx_t a_ctx; // A的ECDH上下文
ec_point_t b_pub; // B的公钥(从网络接收)
unsigned char shared_key[32]; // 共享密钥(256位曲线对应32字节)
size_t shared_key_len = sizeof(shared_key);
// 1. 初始化A的ECDH上下文(约定曲线为secp256r1)
ecdh_ctx_init(&a_ctx, ELLIPTIC_CURVE_SECP256R1);
// 2. A生成密钥对(私钥a_ctx.priv_key,公钥a_ctx.pub_key)
ecdh_generate_key(&a_ctx);
// 3. 发送A的公钥a_ctx.pub_key给B,接收B的公钥b_pub(网络传输逻辑省略)
send_pub_key(&a_ctx.pub_key);
recv_peer_pub(&b_pub);
// 4. A计算共享密钥
int ret = ecdh_compute_shared_key(&a_ctx, &b_pub, shared_key, &shared_key_len);
if (ret == 0) {
printf("共享密钥生成成功(长度:%zu字节):", shared_key_len);
print_hex(shared_key, shared_key_len); // 打印共享密钥(十六进制)
}
// 5. 释放资源
ecdh_ctx_clear(&a_ctx);
ec_point_clear(&b_pub);
}
四、ECDH 的 “安全注意事项”
- 曲线参数必须安全:不能使用自定义或弱曲线(比如曾经被质疑有后门的
secp256k1之外的小众曲线),优先选择标准曲线(secp256r1、secp384r1); - 私钥必须随机:私钥
d的生成必须用 “密码学安全的随机数生成器”(比如 openHiTLS 的bignum_rand_range),不能用时间戳、PID 等可预测的信息; - 共享密钥需后续处理:
ecdh_compute_shared_key输出的 “x 坐标” 是原始数据,建议用 KDF(比如 HKDF)处理(加入盐值、信息参数),避免相同的密钥在不同场景中重复使用; - 推荐用 ECDHE:实际应用中更常用 “临时 ECDH(ECDHE)”—— 每次会话生成新的密钥对,即使私钥泄露,也只影响当前会话,安全性更高(TLS 1.3 默认用 ECDHE)。
五、总结:ECDH 的核心价值
ECDH 用 “椭圆曲线的数学特性” 解决了 “密钥交换难题”,它的核心不是 “加密数据”,而是 “安全地协商共享密钥”—— 最终还是要靠对称加密(AES)来传输数据,但 ECDH 确保了 “钥匙” 不会被偷走。
而 openHiTLS 的实现则体现了 “轻量级、可剪裁” 的设计理念:通过模块化的结构体(ecdh_ctx_t、ec_group_t)和清晰的函数分工(生成密钥对、计算共享密钥),让开发者能快速集成 ECDH 功能,同时兼顾安全性和性能。
无论是手机支付、物联网设备通信,还是 TLS 协议握手,ECDH 都是背后 “默默工作的密钥协商专家”—— 理解它,就能更清楚 “网络安全” 的底层逻辑。
1146

被折叠的 条评论
为什么被折叠?



