You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:03:26