使用 openssl 进行 DH 密钥交换
		
		在我之前的博客中聊到了 DH 密钥交换的原理 . 这里来讨论如何使用 OPENSSL 进行 DH 密钥交换。
一 原理验证
OPENSSL 提供了一系列API, 可以在方便地进行大数计算。在这里我们先对 DH 密钥交换的原理 中讲到的原理进行代码级别的验证。对原理不感兴趣的可以跳过这一小节。
由于在 DH 密钥交换过程中,大部分的操作过程都是一样的,可以定义一个基类来抽象:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | class DHBase { public:     DHBase() : m_pri_key(NULL) {}     virtual ~DHBase() {         if (m_pri_key)             BN_free(m_pri_key);     };     const BIGNUM *GetSharedNum() {         if (!m_pri_key)             GenRandomData();         BN_CTX *ctx = BN_CTX_new();         const BIGNUM *g = DH_get0_g(m_dh);         const BIGNUM *p = DH_get0_p(m_dh);         BIGNUM *r = BN_new();         // r = g ^ m_pri_key % p         int rst = BN_mod_exp(r, g, m_pri_key, p, ctx);         assert(rst == 1);         BN_CTX_free(ctx);         return r;     }     BIGNUM *GenKey(const BIGNUM *shared_num) {         if (!m_pri_key)             GenRandomData();         BIGNUM *key = BN_new();         BN_CTX *ctx = BN_CTX_new();         // key = shared_num ^ m_pri_key % p         int rst = BN_mod_exp(key, shared_num, m_pri_key, DH_get0_p(m_dh), ctx);         assert(rst == 1);         BN_CTX_free(ctx);         std::cout << m_name << ": received shared num: " << GetNumString(shared_num) << std::endl;         std::cout << m_name << ": generated KEY: " << GetNumString(key) << std::endl;         return key;     }     std::string GetName() {         return m_name;     }     static std::string GetNumString(const BIGNUM *num) {         return BN_bn2hex(num);     } private:     void GenRandomData() {         assert(m_dh);         m_pri_key = BN_new();         int rst = BN_rand_range(m_pri_key, DH_get0_p(m_dh));         assert(rst == 1);         while (BN_is_zero(m_pri_key)) {             BN_rand_range(m_pri_key, DH_get0_p(m_dh));         }         std::cout << m_name << ": generated the private num" << std::endl;     } protected:     DH *m_dh;     std::string m_name; private:     BIGNUM *m_pri_key; }; | 
- m_pri_key是双方生成的私密随机数。
- GetSharedNum()返回双方交换的中间变量- G^A mod P
- GenKey()即完成了密钥交换,返回交换成功后的密钥。
DH交换发起方的类如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class DHRequest : public DHBase { public:     DHRequest()             : m_dh_nid(NID_ffdhe8192) {         m_dh = DH_new_by_nid(m_dh_nid);         m_name = "REQUEST";     }     int GetDHNid() {         return m_dh_nid;     } private:     int m_dh_nid; }; | 
m_dh_nid 是 OID (ObjectID 对象标识) 的延伸。OID 是由国际电信联盟(ITU)和ISO/IEC标准化的标识符机制,用于以全局明确的持久名称命名任何对象、概念或“事物”的一组数字。NID (NameID) 则是与 OID 相对应的一个字符串名称;在 DH 密钥交换中,NID 可以是 NID_ffdhe2048, NID_ffdhe3072, NID_ffdhe4096, NID_ffdhe6144 或 NID_ffdhe8192 中的一种。每一种 NID 背后都是由密码学专家或组织精心设计的一组数据 DH(内容包括 G,P等)。当然,我们也可以设计使用自己的数据。
密钥交换接受方的类如下:
| 1 2 3 4 5 6 7 8 9 | class DHResponse : public DHBase { public:     DHResponse(int dh_nid) {         m_name = "RESPONSE";         m_dh = DH_new_by_nid(dh_nid);         std::cout << m_name << ": received DH param (G,P)" << std::endl;     } }; | 
最后来看 DH 密钥交换的过程:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | int main() {     init_openssl();     DHRequest request;     DHResponse response(request.GetDHNid());     const BIGNUM *req_shared_num = request.GetSharedNum();     const BIGNUM *res_shared_num = response.GetSharedNum();     BIGNUM *request_key = request.GenKey(res_shared_num);     BIGNUM *response_key = response.GenKey(req_shared_num);     int cmp = BN_cmp(request_key, response_key);     assert(cmp == 0);     clean_openssl();     return 0; } | 
可以看到,整个过程中,交换的数据只有 nid 和 shared_num 。最终双方生成的 key 是相同的,完成了密钥交换。
| 1 2 3 4 5 6 7 | RESPONSE: received DH param (G,P) REQUEST: generated the private num RESPONSE: generated the private num REQUEST: received shared num: 04066791E05CEDC8C630544043CA2FE9 ... 2B541ED73 REQUEST: generated KEY: 56E45AFDF14856C858899D5E13890693 ... 19066BBD1 RESPONSE: received shared num: 4A7B96C1D952DEC5486FF95DC3FF749D ... B80492A83 RESPONSE: generated KEY: 56E45AFDF14856C858899D5E13890693 ... 19066BBD1 | 
二. OPENSSL 进行 DH 密钥交换
最主要的结构体为 typedef struct dh_st DH; , 原型 dh_st 定义在文件 dh_locl.h 中:
| 1 2 3 4 5 6 7 8 | struct dh_st {     // ...     BIGNUM *p;     BIGNUM *g;     BIGNUM *pub_key; /* g^x % p */     BIGNUM *priv_key; /* x */     // ... }; | 
DH 的生成有多种方式,如前面说的 DH_new_by_nid, 还有后面用到的 DH_generate_parameters_ex .此外还有从 BIO 等生成 DH 的方法。
| 1 2 3 | int DH_generate_parameters_ex(DH *dh, int prime_len,int generator, BN_GENCB *cb); int DH_generate_key(DH *dh); int DH_compute_key(unsigned char *key, BIGNUM *pub_key, DH *dh); | 
- DH_generate_parameters_ex: 用于生成一个随机的 DH 结构体- 参数 prime_len是生成的质数 p 的位长度。
- 参数 generator是一个大于 1 的很小的数,一般是2 或 5。
 
- 参数 
- DH_generate_key: 用于生成公钥和私钥, 不要和 RSA 的公私钥弄混,这里的公钥是指和双方交换的数 G^a % P, 这里的私钥指双方保留的私密数字
- DH_compute_key用于计算最后交换的结果密钥- 参数 key即是结果密钥。其长度可以使用DH_size()计算
- 参数 pub_key是从对方获取的交换数。
 
- 参数 
一个密钥交换的示例如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | #include <openssl/dh.h> #include <openssl/evp.h> #include <openssl/bn.h> class Request { public:     Request() {         m_dh = DH_new();         int rst = DH_generate_parameters_ex(m_dh, 512, 2, NULL);         rst = DH_check(m_dh, &rst);         assert(rst == 1);         DH_generate_key(m_dh);     }     ~Request() {         if (m_dh)             DH_free(m_dh);     }     void GetSharedNums(BIGNUM *&p, BIGNUM *&g) {         assert(m_dh);         p = BN_dup(DH_get0_p(m_dh));         g = BN_dup(DH_get0_g(m_dh));     }     void GetPubKey(BIGNUM *&pubkey) {         assert(m_dh);         pubkey = BN_dup(DH_get0_pub_key(m_dh));     }     std::string GetPriKey(const BIGNUM *pubkey) {         int sz = DH_size(m_dh);         unsigned char *key = (unsigned char *) malloc(sz);         DH_compute_key(key, pubkey, m_dh);         std::string ret(reinterpret_cast<char *>(key));         free(key);         return ret;     } private:     DH *m_dh; }; class Response { public:     Response() : m_dh(NULL) {}     ~Response() {         if(m_dh)             DH_free(m_dh);     }     bool Init(BIGNUM *p, BIGNUM *g) {         if (!m_dh) {             m_dh = DH_new();         }         int rst = DH_set0_pqg(m_dh, p, 0, g);         assert(rst == 1);         rst = DH_generate_key(m_dh);         return rst == 1;     }     void GetPubKey(BIGNUM *&pubkey) {         assert(m_dh);         assert(DH_get0_priv_key(m_dh));         pubkey = BN_dup(DH_get0_pub_key(m_dh));     }     std::string GetPriKey(const BIGNUM *pubkey) {         assert(m_dh);         unsigned char *key = (unsigned char *) malloc(DH_size(m_dh));         int rst = DH_compute_key(key, pubkey, m_dh);         if(rst < 0){             printf("%s", ERR_reason_error_string(ERR_get_error()));         }         std::string ret(reinterpret_cast<char *>(key));         free(key);         return ret;     } private:     DH *m_dh; }; int main(void) {     //init_openssl();     DH *m_dh = DH_new();     int rst = DH_generate_parameters_ex(m_dh, 512, 2, NULL);     rst = DH_check(m_dh, &rst);     assert(rst == 1);     const BIGNUM *ppp = DH_get0_p(m_dh);     BIGNUM *x = BN_dup(ppp);     Request request;     BIGNUM *p = NULL, *g = NULL;     request.GetSharedNums(p, g);     Response response;     bool init = response.Init(p, g);     assert(init);     BIGNUM *pubkey_of_request = NULL, *pubkey_of_response = NULL;     request.GetPubKey(pubkey_of_request);     response.GetPubKey(pubkey_of_response);     std::string key1 = request.GetPriKey(pubkey_of_response);     std::string key2 = response.GetPriKey(pubkey_of_request);     int cmp = key1.compare(key2);     assert(cmp == 0);     //clean_openssl();     return 0; } |