Windows:Diffe-Hellman框架

1. 前言

通过调用windows API使用Diffe-Hellman协议来进行密钥交换,写下这个博客以便今后需要是回来进行考古。在微软的MSDN文档中给出了详细的过程描述。

由于是一个协议涉及到双方之间的数据交换,网络通信是最常用的方式。相关的Sever/Client通行的框架在此给出在此给出

2. DIffe-Hellman简单介绍

Diffe-Hellman协议。两个大素数g和p(p至少512 bits)。g和p这两个素数不需要保密,可以公开。通信双方选择大的随机数a和b,需要保密。计算:
A = g a m o d    p B = g b m o d    p \begin{aligned} A=g^a\mod p\\ B=g^b\mod p \end{aligned} A=gamodpB=gbmodp

计算之后将A和B分别发往对方。则共享密钥为:
K = g a b m o d    p K=g^{ab}\mod p K=gabmodp
A使用如下公式进行计算:
K = B a m o d    p = ( g b ) a m o d    p K=B^a\mod p=(g^b)^a\mod p\\ K=Bamodp=(gb)amodp
B使用如下公式进行计算:
K = A b m o d    p = ( g a ) b m o d    p K=A^b\mod p=(g^a)^b\mod p K=Abmodp=(ga)bmodp
Diffe-Hellman的理论公式来看当相应的大数运行都已经准备好时,实现起来应该是很简单的。

3. Server

3.1 生成Diffe-Hellman公私钥对

通过CryptAcquireContext函数的szProvider参数传入MS_DEF_DH_SCHANNEL_PROVdwProvType参数传入PROV_DH_SCHANNEL即可获得一个Diffe-Hellman协议的CSP

CryptAcquireContext(&hProv, ContainName, MS_DEF_DH_SCHANNEL_PROV, PROV_DH_SCHANNEL, 0);

服务端需要生成P和G,所以在dwFlags参数选择了CRYPT_EXPORTABLE设置为可导出的形式,而不是像客户端中那样设置为CRYPT_PREGEN进行导入。

CryptGenKey(hProv, CALG_DH_EPHEM, CRYPT_EXPORTABLE, &hDHKey);

3.2 导出A,P和G

MSDN文档中给出了Diffe-Hellman创建的公私钥结构。下图是截取其中的相关部分的内容。在服务端生成公钥A中只保存了一个A,即 g a m o d    p g^a\mod p gamodp,而P和G的相关信息没有保存在公钥中,所以需要单独将P和G从私钥中导出。发送过程中需要将公钥A,两个因子P和G分别发送。
公钥结构
导出公钥G可以直接使用CryptExportKey函数进行,将之前生成密钥是获得的句柄hDHkey作为目标参数传入,同时在dwBlobType参数中选择PUBLICKEYBLOB选择导出的密钥类型为公钥。为了确保这里能够顺利导出前面在使用CryptGenKey函数时需要CRYPT_EXPORTABLE参数被顺利传递。

CryptExportKey(hDHKey, NULL, PUBLICKEYBLOB, 0, pbPublicKey, &dwDataLen);

私钥中的内容不应该全部的导出,里面存在了X等一些关键数据,这些是在进行传输时不需要的信息。这里选择使用CryptGetKeyParam函数来将私钥中的P和G导出。只需要将函数的第二个参数设置为KP_PKP_G就能够将相关部分导出。

CryptGetKeyParam(hDHKey, KP_P, pbPKey, &dwDataLen, 0);

3.3 导入客户端公钥B

通过TCP网络接收到了客户端传递过来的公钥B,需要在服务端计算出K,导入的过程中遇到了一个问题:由于服务端需要通过线程进行多任务处理,所以密钥生成和密钥导入分别位于两个函数中,在设计函数接口时没有预留给CSP句柄,所以在导入是又通过CryptAcquireContext函数来申请句柄,但是会出现NET_BAD_KEY错误代码。

出现上述问题的原因可以在MSDN关于CryptAcquireContextA描述中找到。
CryptAcquireContext
通过CryptAcquireContext函数是无法重新获得操作私钥的权限句柄的,所以需将之前获得的CSP句柄传递给导入部分的函数,由于对于函数接口的更改是比较麻烦的,所以就直接使用全局变量来传递CSP了。

HCRYPTKEY hDHKey;
HCRYPTPROV hProv = NULL;

只要对于CSP的获取没有问题就不会出现可以很容易的将客户端的公钥B导入到服务端中,形成他们之间的共享密钥K

CryptImportKey函数的第四个参数hPubKey传递之前那保存了服务端自身私钥的句柄即可。

CryptImportKey(hProv, pbYKey->pbData, pbYKey->length, hDHKey, 0, &hAESKey)

4. Client

4.1 导入P、G,生成X

P、G的获取可以通过各种形式,只要能够传递即可,在协议中这两个数值是不用进行保护的,可以进行公开,这里是使用TCP的方式进行传输。

通过CryptAcquireContext函数的szProvider参数传入MS_DEF_DH_SCHANNEL_PROVdwProvType参数传入PROV_DH_SCHANNEL即可获得一个Diffe-Hellman协议的CSP

CryptAcquireContext(&hProv, ContainName, MS_DEF_DH_SCHANNEL_PROV, PROV_DH_SCHANNEL, 0);

由于P和G是服务端生成并公开的,客户端生成密钥是应该采用CRYPT_PREGEN的预定义形式,随后需要将相应的参数导入到密钥之中。

CryptGenKey(hProv, CALG_DH_EPHEM, CRYPT_PREGEN, &hDHKey);

将从服务端获取到的P存储到一个CRYPT_DATA_BLOB的结构体中,该结构体将cbData设置为大小,pbData指向数据所在的缓冲区。

CRYPT_DATA_BLOB blob;
blob.cbData = iResult, blob.pbData = (BYTE*)buf;
CryptSetKeyParam(hDHKey, KP_P, (BYTE*)&blob, 0);

G和X的导入与P相似只是将KP_P参数替换为了KP_GKP_X。由于X参数是随机生成的,所以在导入X参数是可以将第三个参数设置为NULL,这样CSP就会随机生成一个X,同时计算出 B = g x m o d    p B=g^x\mod p B=gxmodp的结果。

4.2 导入服务端公钥A

在客户端导入服务端的公钥A时使用CryptImportKey,将hPubKey参数设置为被导入的地方,也就是之前通过CryptGenKey生成的句柄。

CryptImportKey(hProv, blob.pbData, blob.cbData, hDHKey, 0, &hAESKey);

导入之后会自动计算出他们之间的协商密钥K,这里的密钥类型为CALG_AGREEDKEY_ANY不能够直接使用需要为其设置加密类型,一开始我向设置为AES的加密类型但是在给出的PROV_DH_SCHANNEL CSP没有支持AES最后选择了与MSDN例子中相同的RC4,这里的hAESKey的命名就没有进行更改。

ALG_ID algid = CALG_RC4;
CryptSetKeyParam(hAESKey, KP_ALGID, (PBYTE)&algid, 0);

导入了服务端的公钥A之后,在客户端就计算出了会话密钥K,这个密钥设置ALG_ID之后就能够作加解密密钥进行使用,使用的方式可以直接调用API接口CryptEecryptCryptDecrypt函数进行。

CryptEncrypt(hKey, NULL, true, 0, (BYTE*)buf, &dwLen, 512);
CryptDecrypt(hAESKey, NULL, true, 0, (BYTE*)buf, &dwDataLen);

5. 完整代码

完整的代码由于使用TCP的方式传输数据,整体较长,故不贴在此处了。代码的链接地址在这,有需求的可以到Gitee上获取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值