在Delphi中使用OpenSSL实现Diffie-Hellman密钥交换的实践
我之前在尝试把Diffie-Hellman密钥交换集成到现有Delphi协议时,也遇到过网上资料只说“双方交换DH参数即可”这种模糊的情况,折腾了好几天才啃下来。后来结合适配Delphi的OpenSSL头文件,对照OpenSSL源码和示例,终于实现了从生成DH参数、公私钥对到计算共享密钥的完整流程。目前我是用文件来做参数和密钥的交换,改成套接字或者其他传输方式也很容易调整。
下面是完整的实现代码和步骤拆解:
核心实现步骤与代码
初始化OpenSSL模块
首先必须初始化EVP、BIO、PEM这些核心模块,确保后续API调用正常:
SSL_InitEVP; SSL_InitBIO; SSL_InitPEM;
生成DH公共参数
这一步生成通信双方共用的DH参数,我这里设置的是1024位素数长度(安全性要求高的话可以换成2048位):
Log('-------------Generating parameters---------------'); var parameters_context: PEVP_PKEY_CTX; parameters: PEVP_PKEY; i: Integer; begin parameters_context := EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nil); if parameters_context <> nil then Log('Context for parameters created') else begin Log('Failed to generate parameters context'); Exit; end; i := EVP_PKEY_paramgen_init(parameters_context); if i = 1 then Log('Parameter context init done') else begin Log('Error initializing parameter context, return=' + IntToStr(i)); Exit; end; // 设置素数长度为1024位,等价于调用EVP_PKEY_CTX_set_dh_paramgen_prime_len i := EVP_PKEY_CTX_ctrl(parameters_context, EVP_PKEY_DH, EVP_PKEY_OP_PARAMGEN, EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN, 1024, nil); if i = 1 then Log('Setting prime length done') else begin Log('Error setting prime length, return=' + IntToStr(i)); Exit; end; i := EVP_PKEY_paramgen(parameters_context, @parameters); if i = 1 then Log('Generation of parameters successful') else begin Log('Error while generating parameters, return=' + IntToStr(i)); Exit; end; end;
传输DH参数给通信方
生成的参数需要传给另一方,这里用文件读写模拟传输,实际项目中可以直接转成字节流通过套接字发送:
Log('-------------Output parameters to file---------------'); var bio_file: PBIO; parameters2: PEVP_PKEY; i: Integer; begin // 写入参数到文件(模拟发送) bio_file := BIO_new_file('C:\DH_parameters_test.pem', 'w'); i := PEM_write_bio_Parameters(bio_file, parameters); Log('PEM write return=' + IntToStr(i)); BIO_free_all(bio_file); Log('-------------Client reading parameters---------------'); // 读取参数(模拟接收) bio_file := BIO_new_file('C:\DH_parameters_test.pem', 'r'); parameters2 := PEM_read_bio_Parameters(bio_file, nil); BIO_free_all(bio_file); if parameters2 <> nil then Log('Read parameters successfully for client') else begin Log('Error reading parameters'); Exit; end; end;
生成双方的公私钥对
服务器和客户端分别用拿到的DH参数生成自己的公私钥对:
// 服务器端生成密钥对 Log('-------------Generating private/public keys on server---------------'); var key_generation_context: PEVP_PKEY_CTX; private_key: PEVP_PKEY; i: Integer; begin key_generation_context := EVP_PKEY_CTX_new(parameters, nil); if key_generation_context <> nil then Log('Created key generation context') else begin Log('Error creating key generation context'); Exit; end; i := EVP_PKEY_keygen_init(key_generation_context); if i = 1 then Log('Initialized key generation successfully') else begin Log('Error initializing key generation, return=' + IntToStr(i)); Exit; end; i := EVP_PKEY_keygen(key_generation_context, @private_key); if i = 1 then Log('Generated keys') else begin Log('Error generating keys, return=' + IntToStr(i)); Exit; end; end; // 客户端生成密钥对 Log('-------------Generating private/public keys on client---------------'); var key_generation_context2: PEVP_PKEY_CTX; private_key2: PEVP_PKEY; i: Integer; begin key_generation_context2 := EVP_PKEY_CTX_new(parameters2, nil); if key_generation_context2 <> nil then Log('Created key generation context on client') else begin Log('Error creating key generation context on client'); Exit; end; i := EVP_PKEY_keygen_init(key_generation_context2); if i = 1 then Log('Initialized key generation successfully on client') else begin Log('Error initializing key generation on client, return=' + IntToStr(i)); Exit; end; i := EVP_PKEY_keygen(key_generation_context2, @private_key2); if i = 1 then Log('Generated keys on client') else begin Log('Error generating keys, return=' + IntToStr(i)); Exit; end; end;
交换公钥并计算共享密钥
双方交换公钥后,用自己的私钥和对方的公钥计算出相同的共享密钥:
Log('-------------Public key exchange---------------'); var bio_file: PBIO; public_key, public_key2: PEVP_PKEY; shared_context1, shared_context2: PEVP_PKEY_CTX; keylen: Cardinal; arr: array of Byte; i: Integer; begin // 服务器输出公钥 Log('-------------Outputing public key on server---------------'); bio_file := BIO_new_file('C:\public_key1.pem', 'w'); i := PEM_write_bio_PUBKEY(bio_file, private_key); Log('PEM write return=' + IntToStr(i)); BIO_free_all(bio_file); // 客户端输出公钥 Log('-------------Outputing public key on client---------------'); bio_file := BIO_new_file('C:\public_key2.pem', 'w'); i := PEM_write_bio_PUBKEY(bio_file, private_key2); Log('PEM write return=' + IntToStr(i)); BIO_free_all(bio_file); // 客户端读取服务器公钥 Log('-------------Reading public key1 (for client)---------------'); bio_file := BIO_new_file('C:\public_key1.pem', 'r'); public_key := PEM_read_bio_PUBKEY(bio_file, nil, nil, nil); BIO_free_all(bio_file); // 服务器读取客户端公钥 Log('-------------Reading public key2 (for server)---------------'); bio_file := BIO_new_file('C:\public_key2.pem', 'r'); public_key2 := PEM_read_bio_PUBKEY(bio_file, nil, nil, nil); BIO_free_all(bio_file); // 客户端计算共享密钥 shared_context1 := EVP_PKEY_CTX_new(private_key2, nil); EVP_PKEY_derive_init(shared_context1); EVP_PKEY_derive_set_peer(shared_context1, public_key); // 先获取共享密钥长度 EVP_PKEY_derive(shared_context1, nil, @keylen); SetLength(arr, keylen); // 生成共享密钥 EVP_PKEY_derive(shared_context1, @arr[0], @keylen); Log('Client shared key generated, length: ' + IntToStr(keylen)); // 服务器计算共享密钥 shared_context2 := EVP_PKEY_CTX_new(private_key, nil); EVP_PKEY_derive_init(shared_context2); EVP_PKEY_derive_set_peer(shared_context2, public_key2); EVP_PKEY_derive(shared_context2, nil, @keylen); SetLength(arr, keylen); EVP_PKEY_derive(shared_context2, @arr[0], @keylen); Log('Server shared key generated, length: ' + IntToStr(keylen)); // 记得释放所有资源 EVP_PKEY_CTX_free(shared_context1); EVP_PKEY_CTX_free(shared_context2); EVP_PKEY_free(public_key); EVP_PKEY_free(public_key2); EVP_PKEY_free(private_key); EVP_PKEY_free(private_key2); EVP_PKEY_free(parameters); EVP_PKEY_free(parameters2); end;
注意事项
- 代码中的文件路径请根据自己的开发环境调整,实际项目中建议用套接字直接传输PEM格式的参数和公钥,无需落地到文件。
- 务必记得释放所有OpenSSL创建的上下文和密钥对象,避免内存泄漏。
- 如果需要更高的安全性,可以将素数长度调整为2048位或4096位,相应的计算时间会略有增加。
内容的提问来源于stack exchange,提问作者Mozzg
相关产品推荐
相关产品推荐

