1. 项目概述:为什么我们需要自己实现一次DH密钥交换?

在信息安全领域,密钥交换协议是构建安全通信的基石。想象一下,你和朋友需要在一个完全不安全的公共信道上(比如一个可以被任何人监听的咖啡馆Wi-Fi)商量一个只有你们俩知道的秘密暗号,用来给后续的通信加密。Diffie-Hellman(DH)密钥交换协议就是这个问题的经典解决方案,它允许双方在不安全的信道上,通过公开对话,共同计算出一个只有双方知道的共享密钥。这个密钥随后可以用来进行对称加密,比如AES,从而保护通信内容。

虽然现在有OpenSSL、LibreSSL等成熟的密码学库,直接调用几个API就能完成密钥交换,但对于学习C++、深入理解密码学原理、甚至为嵌入式或特定性能场景定制化开发的程序员来说,亲手实现一次DH协议是一次不可多得的“练内功”的机会。它能让你透彻理解模幂运算、大数处理、随机数生成、协议流程等核心概念,这些知识是理解TLS/SSL、SSH等现代安全协议的基础。本次,我们就用C++从零开始,实现一个简化但完整的DH密钥交换流程,并附上可编译运行的源码。

2. 核心原理与设计思路拆解

2.1 Diffie-Hellman密钥交换的数学核心

DH协议的安全性建立在 离散对数问题 的计算困难性上。简单来说,给定一个素数 p 、一个基数 g (通常是 p 的一个原根),以及 g^a mod p 的结果,想要反推出指数 a 是非常困难的,当 p 是一个很大的素数(比如2048位)时,即使对于现代计算机,计算离散对数在实践上也是不可行的。

协议流程可以类比为一个“颜色混合”的经典例子:

  1. 公共参数协商 :通信双方Alice和Bob先公开约定两个数:一个大素数 p 和一个整数 g g 是模 p 下的原根)。这就像公开约定好使用“黄色”和“蓝色”这两种基础颜料。
  2. 生成私钥 :Alice秘密地选择一个随机大数 a 作为她的私钥;Bob秘密地选择另一个随机大数 b 作为他的私钥。 a b 绝不公开。
  3. 计算并交换公钥
    • Alice计算她的公钥 A = g^a mod p
    • Bob计算他的公钥 B = g^b mod p
    • 然后,双方通过网络交换公钥 A B 。即使窃听者Eve拿到了 p , g , A , B ,她也很难算出 a b
  4. 计算共享密钥
    • Alice收到Bob的公钥 B 后,计算共享密钥 S = B^a mod p = (g^b)^a mod p = g^(b*a) mod p
    • Bob收到Alice的公钥 A 后,计算共享密钥 S = A^b mod p = (g^a)^b mod p = g^(a*b) mod p
    • 由于数学上的结合律,双方独立计算出了相同的 S ,即 g^(ab) mod p 。这个 S 就是只有他们俩知道的共享秘密,后续可以从中提取出对称加密的密钥。

注意 :这里使用的是最基本的DH算法。在实际应用中(如TLS),更常用的是基于椭圆曲线的ECDHE( ephemeral),它提供了更强的安全性和更小的密钥尺寸,但基本原理相通。

2.2 我们的C++实现方案设计

基于上述原理,我们的实现需要解决几个关键问题:

  1. 大整数运算 p , g , a , b 都是非常大的整数(至少数百位),C++内置的 int long long 类型远远不够。我们需要一个大数运算库。
  2. 随机数生成 :私钥 a b 必须是密码学安全的随机数,不能使用普通的 rand() 函数。
  3. 模幂运算 :高效计算 g^a mod p ,这是核心操作。
  4. 协议流程封装 :将上述步骤封装成清晰的类或函数,模拟Alice和Bob的交互过程。

方案选型

  • 大数库 :为了专注于协议逻辑而非底层大数实现,我们选择使用 GMP(GNU Multiple Precision Arithmetic Library) 。它是一个成熟、高效、开源的大数运算库,广泛用于密码学和数学计算。我们将通过C++包装器 gmpxx.h 来使用它,这样代码更简洁。
  • 随机数 :GMP库提供了密码学安全的伪随机数生成函数,我们将使用 gmp_randclass
  • 开发环境 :代码将在Linux/macOS下使用GCC/Clang编译,或在Windows下使用MinGW-w64编译。我们也会说明如何在Visual Studio中配置。

3. 环境准备与核心工具解析

3.1 安装GMP库

GMP库是我们的基石。以下是在不同系统下的安装方法:

Linux (Ubuntu/Debian):

sudo apt-get update
sudo apt-get install libgmp-dev

macOS (使用Homebrew):

brew install gmp

Windows (使用MSYS2 + MinGW-w64):

  1. 安装MSYS2。
  2. 打开 MSYS2 MinGW 64-bit 终端。
  3. 运行: pacman -S mingw-w64-x86_64-gmp

安装后,编译器需要知道头文件和库文件的位置。通常包管理器会处理好,如果遇到链接错误,可能需要手动指定 -lgmp -lgmpxx 链接选项。

3.2 项目结构与代码设计

我们将创建两个主要的类来模拟通信双方,并有一个用于共享公共参数的结构体。

dh_key_exchange/
├── dh_common.hpp    // 公共参数和工具函数声明
├── dh_common.cpp    // 公共参数和工具函数实现
├── dh_participant.hpp // 通信参与者类声明
├── dh_participant.cpp // 通信参与者类实现
├── main.cpp         // 模拟Alice和Bob交换密钥的主程序
└── Makefile (或 CMakeLists.txt)

dh_common.hpp/cpp :定义公共参数 DHParams 结构体(包含 p g ),并提供一个函数来生成或加载标准的、安全的DH参数。在实际项目中,这些参数通常是预定义好的(如RFC 3526中定义的2048位MODP组),我们这里为了演示,会生成一个较小但安全的参数。

dh_participant.hpp/cpp :定义 DHParticipant 类。每个实例代表协议中的一方(Alice或Bob)。这个类应该:

  • 在构造函数中接受 DHParams 并初始化随机状态。
  • 有一个 generate_key_pair() 方法,用于生成私钥和计算公钥。
  • 有一个 compute_shared_secret() 方法,根据对方的公钥计算共享密钥。
  • 提供获取公钥、获取共享密钥的方法。

main.cpp :创建两个 DHParticipant 对象(Alice和Bob),模拟完整的密钥交换流程,并验证他们计算出的共享密钥是否相同。

4. 核心代码实现与逐行解析

4.1 公共参数定义与工具 (dh_common.hpp/cpp)

首先,我们定义公共参数结构体和必要的类型别名。

dh_common.hpp:

#ifndef DH_COMMON_HPP
#define DH_COMMON_HPP

#include <gmpxx.h> // GMP C++ 包装器

// 使用GMP的mpz_class作为大整数类型,它自动管理内存
using BigInt = mpz_class;

// Diffie-Hellman 公共参数结构体
struct DHParams {
    BigInt p; // 大素数模数
    BigInt g; // 生成元(基数)

    DHParams() = default;
    DHParams(const BigInt& prime, const BigInt& generator) : p(prime), g(generator) {}
};

// 函数声明:生成一组用于演示的DH参数(注意:非密码学强度,仅用于教学)
// 在实际应用中,应使用标准化的、足够大的素数群(如2048位的RFC3526 MODP组)
DHParams generate_demo_params();

#endif // DH_COMMON_HPP

dh_common.cpp:

#include "dh_common.hpp"
#include <iostream>

DHParams generate_demo_params() {
    DHParams params;

    // 警告:这是一个非常小的、仅用于演示和测试的素数。
    // 真正的安全应用必须使用至少2048位的大素数。
    params.p = "23"; // 一个小素数,方便理解和调试
    params.g = "5";  // 模23下的一个原根

    // 在实际实现中,这里应该从文件加载预计算好的标准参数,
    // 或者使用GMP的 `mpz_nextprime` 和原根检测算法生成大参数。
    // 例如:mpz_nextprime(params.p.get_mpz_t(), some_large_number);

    std::cout << "[INFO] 使用演示参数: p = " << params.p << ", g = " << params.g << std::endl;
    std::cout << "[WARNING] 这些参数强度不足,仅用于算法演示!" << std::endl;
    return params;
}

实操心得 :在测试和调试阶段,使用像23这样的小素数非常有用。你可以手动计算 g^a mod p 来验证你的代码是否正确。一旦逻辑正确,再替换成从标准文件(如OpenSSL的 dhparam 命令生成的文件)中加载的大素数。

4.2 通信参与者类的实现 (dh_participant.hpp/cpp)

这是整个项目的核心,封装了单个参与者的所有状态和行为。

dh_participant.hpp:

#ifndef DH_PARTICIPANT_HPP
#define DH_PARTICIPANT_HPP

#include "dh_common.hpp"
#include <gmpxx.h>
#include <gmp.h>

class DHParticipant {
public:
    // 构造函数:需要传入DH公共参数
    explicit DHParticipant(const DHParams& params);

    // 生成密钥对:私钥(秘密保存)和公钥(可以发送给对方)
    void generate_key_pair();

    // 获取生成的公钥
    BigInt get_public_key() const { return public_key_; }

    // 根据对方的公钥计算共享密钥
    void compute_shared_secret(const BigInt& other_public_key);

    // 获取计算出的共享密钥
    BigInt get_shared_secret() const { return shared_secret_; }

private:
    DHParams params_;          // 公共参数
    gmp_randclass rand_state_; // GMP随机数状态
    BigInt private_key_;       // 私钥 a 或 b
    BigInt public_key_;        // 公钥 A = g^a mod p 或 B = g^b mod p
    BigInt shared_secret_;     // 共享密钥 S
};

#endif // DH_PARTICIPANT_HPP

dh_participant.cpp:

#include "dh_participant.hpp"
#include <iostream>
#include <chrono>

DHParticipant::DHParticipant(const DHParams& params)
    : params_(params),
      // 使用当前时间作为种子初始化随机数生成器
      rand_state_(gmp_randinit_default) {
    unsigned long seed = std::chrono::system_clock::now().time_since_epoch().count();
    rand_state_.seed(seed);
    std::cout << "[DEBUG] DHParticipant 初始化,随机种子已设置。" << std::endl;
}

void DHParticipant::generate_key_pair() {
    // 1. 生成私钥:一个在 [1, p-2] 范围内的随机大整数
    //    p-2 是为了确保私钥非零且小于p-1(根据费马小定理,g^(p-1) mod p = 1)
    mpz_class range = params_.p - 2;
    private_key_ = rand_state_.get_z_range(range) + 1; // +1 使范围变为 [1, p-1]

    // 2. 计算公钥:public_key = g^private_key mod p
    mpz_powm(public_key_.get_mpz_t(),          // 结果
             params_.g.get_mpz_t(),            // 底数 g
             private_key_.get_mpz_t(),         // 指数 private_key
             params_.p.get_mpz_t());           // 模数 p

    std::cout << "[DEBUG] 密钥对已生成。私钥(保密): " << private_key_
              << ", 公钥: " << public_key_ << std::endl;
}

void DHParticipant::compute_shared_secret(const BigInt& other_public_key) {
    // 计算共享密钥:shared_secret = other_public_key^private_key mod p
    // 即 S = B^a mod p (对于Alice) 或 S = A^b mod p (对于Bob)
    mpz_powm(shared_secret_.get_mpz_t(),        // 结果
             other_public_key.get_mpz_t(),      // 底数(对方的公钥)
             private_key_.get_mpz_t(),          // 指数(自己的私钥)
             params_.p.get_mpz_t());            // 模数 p

    std::cout << "[DEBUG] 已计算共享密钥: " << shared_secret_ << std::endl;
}

关键点解析

  1. 随机数生成 gmp_randclass get_z_range 提供了在指定范围内生成均匀分布随机大数的能力。我们使用系统时间作为种子,对于演示足够。在生产环境中,可能需要更安全的熵源(如 /dev/urandom )。
  2. 模幂运算 mpz_powm 是GMP库中用于计算 base^exp mod mod 的函数,它使用了平方乘法等优化算法,效率远高于先求幂再取模。 这是整个协议中计算量最大的操作,也是安全性的核心所在。
  3. 私钥范围 :私钥范围选为 [1, p-2] 是常见的做法。理论上 [1, p-1] 都可以,但选择 p-2 可以避免私钥等于 p-1 时,根据费马小定理公钥恒为1的特殊情况(虽然概率极低)。

4.3 主程序模拟交换流程 (main.cpp)

现在,我们把所有部分组合起来,模拟Alice和Bob的完整交互。

#include "dh_common.hpp"
#include "dh_participant.hpp"
#include <iostream>
#include <cassert>

int main() {
    std::cout << "=== C++ Diffie-Hellman 密钥交换模拟 ===\n" << std::endl;

    // 第1步:双方协商公共参数(在现实中,这部分通常是预定义或协商的)
    DHParams params = generate_demo_params();
    std::cout << "\n--- 公共参数协商完成 ---\n" << std::endl;

    // 第2步:创建通信双方 Alice 和 Bob
    DHParticipant alice(params);
    DHParticipant bob(params);

    // 第3步:双方各自生成自己的密钥对(私钥+公钥)
    std::cout << "\n--- Alice 生成密钥对 ---" << std::endl;
    alice.generate_key_pair();

    std::cout << "\n--- Bob 生成密钥对 ---" << std::endl;
    bob.generate_key_pair();

    // 第4步:双方交换公钥(模拟网络传输)
    BigInt alice_public_key = alice.get_public_key();
    BigInt bob_public_key = bob.get_public_key();
    std::cout << "\n--- 公钥交换 ---" << std::endl;
    std::cout << "Alice 将她的公钥发送给 Bob: " << alice_public_key << std::endl;
    std::cout << "Bob 将他的公钥发送给 Alice: " << bob_public_key << std::endl;

    // 第5步:双方使用对方的公钥和自己的私钥计算共享密钥
    std::cout << "\n--- 计算共享密钥 ---" << std::endl;
    alice.compute_shared_secret(bob_public_key); // Alice 用 Bob 的公钥计算
    bob.compute_shared_secret(alice_public_key); // Bob 用 Alice 的公钥计算

    // 第6步:验证双方计算出的共享密钥是否相同
    BigInt alice_secret = alice.get_shared_secret();
    BigInt bob_secret = bob.get_shared_secret();

    std::cout << "\n=== 验证结果 ===" << std::endl;
    std::cout << "Alice 的共享密钥: " << alice_secret << std::endl;
    std::cout << "Bob 的共享密钥:   " << bob_secret << std::endl;

    if (alice_secret == bob_secret) {
        std::cout << "\n✅ 成功!Alice 和 Bob 协商出了相同的共享密钥。" << std::endl;
        std::cout << "这个密钥可以用于后续的对称加密(如AES)。" << std::endl;
    } else {
        std::cout << "\n❌ 失败!共享密钥不匹配。协议实现可能有错误。" << std::endl;
        return 1;
    }

    // 简单演示:如何将共享密钥转换为一个固定长度的字节串(例如用于AES-256)
    // 注意:直接使用mpz_class的二进制表示可能不够均匀,实际中会使用KDF(密钥派生函数)
    std::cout << "\n--- 共享密钥处理示例 ---" << std::endl;
    // 获取密钥的16进制字符串表示
    std::string secret_hex = alice_secret.get_str(16);
    std::cout << "共享密钥(16进制): " << secret_hex << std::endl;
    // 在实际应用中,这里会调用如HKDF、PBKDF2等函数,从共享密钥派生出一个或多个加密密钥。

    return 0;
}

4.4 编译与运行

创建一个简单的 Makefile 来编译项目:

CXX = g++
CXXFLAGS = -std=c++11 -O2 -Wall -Wextra
LDFLAGS = -lgmp -lgmpxx

TARGET = dh_exchange
SOURCES = main.cpp dh_common.cpp dh_participant.cpp
HEADERS = dh_common.hpp dh_participant.hpp
OBJECTS = $(SOURCES:.cpp=.o)

all: $(TARGET)

$(TARGET): $(OBJECTS)
	$(CXX) $(OBJECTS) -o $(TARGET) $(LDFLAGS)

%.o: %.cpp $(HEADERS)
	$(CXX) $(CXXFLAGS) -c $< -o $@

clean:
	rm -f $(OBJECTS) $(TARGET)

run: $(TARGET)
	./$(TARGET)

.PHONY: all clean run

在终端中,进入项目目录,执行:

make run

你将看到类似以下的输出:

=== C++ Diffie-Hellman 密钥交换模拟 ===

[INFO] 使用演示参数: p = 23, g = 5
[WARNING] 这些参数强度不足,仅用于算法演示!

--- 公共参数协商完成 ---

[DEBUG] DHParticipant 初始化,随机种子已设置。
[DEBUG] DHParticipant 初始化,随机种子已设置。

--- Alice 生成密钥对 ---
[DEBUG] 密钥对已生成。私钥(保密): 15, 公钥: 19

--- Bob 生成密钥对 ---
[DEBUG] 密钥对已生成。私钥(保密): 3, 公钥: 10

--- 公钥交换 ---
Alice 将她的公钥发送给 Bob: 19
Bob 将他的公钥发送给 Alice: 10

--- 计算共享密钥 ---
[DEBUG] 已计算共享密钥: 2
[DEBUG] 已计算共享密钥: 2

=== 验证结果 ===
Alice 的共享密钥: 2
Bob 的共享密钥:   2

✅ 成功!Alice 和 Bob 协商出了相同的共享密钥。
这个密钥可以用于后续的对称加密(如AES)。

--- 共享密钥处理示例 ---
共享密钥(16进制): 2

你可以手动验证: p=23, g=5 。Alice私钥 a=15 ,公钥 A = 5^15 mod 23 。计算 5^15 = 30517578125 30517578125 / 23 = 132685122... ,余数确实是19。Bob私钥 b=3 ,公钥 B = 5^3 mod 23 = 125 mod 23 = 10 。共享密钥:Alice侧 S = B^a mod p = 10^15 mod 23 = 2 ;Bob侧 S = A^b mod p = 19^3 mod 23 = 6859 mod 23 = 2 。验证成功!

5. 进阶讨论与安全强化

5.1 从演示到实战:关键安全升级

我们上面的实现是一个清晰的原理演示,但距离生产级应用还有巨大差距。以下是必须考虑的安全强化点:

  1. 使用标准化的、足够大的素数群

    • 绝对不要 在真实项目中使用自己生成的、未经审查的 p g
    • 必须使用行业标准,如 RFC 3526 中定义的 2048 位或 4096 位 MODP 群。这些素数经过精心挑选,确保 g 是合适的生成元,并且群的结构能抵抗某些密码学攻击(如小子群攻击)。
    • 实践中,可以从文件加载这些参数,或者使用密码学库(如OpenSSL)提供的标准组。
  2. 私钥的随机性

    • 演示中使用时间作为种子是不够的。必须使用密码学安全的随机数生成器(CSPRNG)。
    • 在Linux/macOS上,应读取 /dev/urandom ;在Windows上,使用 CryptGenRandom BCryptGenRandom
    • GMP的 gmp_randinit_default 默认使用Mersenne Twister算法,它不是密码学安全的。虽然GMP提供了 gmp_randinit_mt 等,但种子来源必须安全。更可靠的做法是使用系统CSPRNG生成一个种子缓冲区,然后用它初始化GMP的随机状态。
  3. 前向安全性

    • 基本的DH协议不具备前向安全性。如果长期私钥 a b 泄露,过去所有用该密钥协商出的会话密钥都可能被破解。
    • 解决方案是使用临时DH(DHE)或基于椭圆曲线的临时DH(ECDHE) 。核心思想是每次会话都生成一对新的临时密钥对,会话结束后立即销毁私钥。这样,即使长期密钥泄露,过去的会话记录也无法解密。TLS 1.3已强制要求使用ECDHE。
  4. 密钥派生与规范化

    • 直接计算出的 g^(ab) mod p 是一个大整数,不适合直接作为AES等对称加密的密钥。
    • 必须使用 密钥派生函数(KDF) ,如 HKDF,将共享秘密转换为一个或多个长度固定、随机性良好的密钥。KDF还能将其他上下文信息(如协议标识、双方身份)混合进去,进一步增强安全性。
  5. 中间人攻击与身份认证

    • 基础的DH协议只解决密钥协商,不解决身份认证。攻击者Mallory可以站在Alice和Bob中间,分别与他们两个进行DH交换,然后转发消息,从而窃听甚至篡改通信。这就是经典的“中间人攻击”。
    • 必须结合身份认证机制 ,如:
      • 数字签名 :双方用各自的长期私钥对交换的DH公钥进行签名。对方用其公钥验证签名。这需要公钥基础设施(PKI)或预先交换并信任公钥。
      • 静态DH :使用长期固定的DH密钥对,但这就失去了前向安全性。
      • PSK(预共享密钥) :在协议中混合进一个双方预先知道的秘密。

5.2 性能优化与生产环境集成

  1. 大数运算优化 :GMP本身已经高度优化。但在极端性能场景下,可以考虑:

    • 使用固定大小的整数(如通过 mpz_import / mpz_export )来避免动态内存分配的开销。
    • 对于椭圆曲线版本(ECDH),有更专用的库(如OpenSSL的EC模块、libsodium、BearSSL),性能更高,密钥更短。
  2. 内存安全 :确保私钥等敏感数据在内存中停留时间最短,使用后尽快用安全的内存清零函数(如 memset_s OPENSSL_cleanse )覆盖。

  3. 集成到网络协议 :真实的DH交换是嵌入在像TLS握手这样的协议中的。你需要定义清晰的网络消息格式来序列化和反序列化 p , g , 公钥 等数据(通常编码为ASN.1 DER或简单的二进制大端格式)。

6. 常见问题与调试技巧实录

在实现和调试DH协议时,我遇到过不少坑。这里分享一些典型问题和解决方法:

问题1:编译错误 “undefined reference to __gmpz_init’ 等。

  • 原因 :链接器找不到GMP库。
  • 解决 :确保编译命令正确链接了 -lgmp -lgmpxx (C++包装器)。如果使用 gmpxx.h ,通常需要链接 -lgmpxx -lgmp 。检查GMP库是否已正确安装到系统路径。

问题2:共享密钥计算不正确,双方得到的结果不同。

  • 排查步骤
    1. 验证公共参数 :首先打印并确认Alice和Bob使用的 p g 完全一致。这是最常见的错误来源。
    2. 手动验算 :使用小参数(如 p=23, g=5 )进行测试。手动计算或写一个简单的Python脚本验证 g^a mod p g^b mod p 是否正确。
    3. 检查模幂函数 :确认你使用的 mpz_powm 函数参数顺序是正确的: mpz_powm(result, base, exponent, modulus)
    4. 检查随机数范围 :确保私钥的生成范围是 [1, p-2] [1, p-1] 。私钥为0会导致公钥为1,私钥等于 p-1 根据费马小定理也会导致公钥为1,可能引发问题。
    5. 检查数据类型 :确保在传递参数给GMP函数时,使用了正确的 get_mpz_t() 方法获取底层 mpz_t 类型。

问题3:程序运行速度很慢,尤其是使用大素数时。

  • 原因 :模幂运算 g^a mod p 是指数级复杂度, a 很大时(通常有几百位)计算量巨大。
  • 分析 :这是正常现象,也是DH协议安全性的体现(离散对数难题)。GMP的 mpz_powm 已经使用了高效的算法(如滑动窗口法)。
  • 优化 :确保编译时开启了优化标志(如 -O2 )。对于生产环境,考虑使用硬件加速(如果CPU支持)或切换到椭圆曲线ECDH,后者在相同安全强度下密钥更短、计算更快。

问题4:如何将共享密钥用于AES加密?

  • 关键 绝对不能 直接将 mpz_class 的字节表示作为AES密钥!共享秘密 S 是一个大整数,其二进制表示的熵分布可能不均匀,长度也不一定是AES-128/256要求的16/32字节。
  • 正确做法 :使用密钥派生函数(KDF)。一个简单的(但非最标准的)方法是使用加密哈希函数:
    #include <openssl/evp.h> // 使用OpenSSL的KDF或哈希
    // 假设 shared_secret 是 mpz_class
    std::string secret_str = shared_secret.get_str(); // 获取十进制字符串表示(或二进制流)
    // 使用SHA-256对 (secret_str + “DH-KDF-Salt”) 进行哈希,得到32字节的AES-256密钥
    unsigned char derived_key[32];
    EVP_Digest(secret_str.data(), secret_str.length(), derived_key, NULL, EVP_sha256(), NULL);
    
  • 生产级做法 :使用标准的KDF,如HKDF。OpenSSL提供了 EVP_PKEY_derive 函数族,可以配合DH对象直接派生出密钥材料。

问题5:在Windows (Visual Studio) 下如何配置?

  1. 下载GMP的Windows预编译库,或者使用vcpkg安装: vcpkg install gmp:x64-windows
  2. 在VS项目中,配置附加包含目录指向GMP的 include 文件夹,配置附加库目录指向 lib 文件夹。
  3. 在链接器输入中,添加 gmp.lib gmpxx.lib (如果使用C++接口)。
  4. 可能需要将GMP的DLL文件(如 gmp-10.dll )放到可执行文件目录或系统路径。

通过这次从零实现,你不仅得到了一个可以运行的DH密钥交换程序,更重要的是,你穿透了API的黑箱,亲手触摸了非对称密码学的核心齿轮。下次当你配置Nginx的 ssl_ciphers 看到 ECDHE-RSA-AES256-GCM-SHA384 时,你会会心一笑,因为你清楚地知道 ECDHE 那部分到底在忙活什么。这就是动手实现的意义——将知识从“听说过”变成“摸得着”。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐