首页 > Note > 使用 openssl 进行 DH 密钥交换

使用 openssl 进行 DH 密钥交换

2019年1月19日 发表评论 阅读评论

在我之前的博客中聊到了 DH 密钥交换的原理 . 这里来讨论如何使用 OPENSSL 进行 DH 密钥交换。

一 原理验证

OPENSSL 提供了一系列API, 可以在方便地进行大数计算。在这里我们先对 DH 密钥交换的原理 中讲到的原理进行代码级别的验证。对原理不感兴趣的可以跳过这一小节。
由于在 DH 密钥交换过程中,大部分的操作过程都是一样的,可以定义一个基类来抽象:

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交换发起方的类如下:

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_ffdhe2048NID_ffdhe3072NID_ffdhe4096NID_ffdhe6144 或 NID_ffdhe8192 中的一种。每一种 NID 背后都是由密码学专家或组织精心设计的一组数据 DH(内容包括 G,P等)。当然,我们也可以设计使用自己的数据。

密钥交换接受方的类如下:

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 密钥交换的过程:

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 是相同的,完成了密钥交换。

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 中:

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 的方法。

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 是从对方获取的交换数。

一个密钥交换的示例如下:

#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;
}

 

 

 

 

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.