ECDH使得交换双方可以在不共享任何秘密的情况下协商出一个密钥。密钥磋商过程:

 假设密钥交换双方为Alice、Bob,有相同的椭圆曲线。

   1) Alice生成随机数私钥a,计算a*G。 生成Alice公钥

   2) Bob生成随机数私钥b,计算b*G。 生成Bob公钥

   3) Alice将公钥a*G和基点G传递给Bob。窃听者C可以获取公钥a*G和基点G。

   4) Bob将b*G传递给Alice。同理,窃听者C同样可以获得b*G。

   5) Bob收到Alice传递过来的公钥a*G,计算Q =baG;

   6) Alice收到Bob传递的公钥b*G,计算Q=abG

     窃听者C可以获得G、aG、bG但是得不到abG

 双方生成密钥对后,密钥对放在EVP_PKEY*pkey所指的对象中。需要将公钥从pkey中取出然后转为8进制数据发送给对方。

  1)从pkey中拿到原始接口EC_KEY格式的密钥

struct ec_key_st *EVP_PKEY_get0_EC_KEY(EVP_PKEY *pkey);

  2)取出公钥,公钥是一个点

EC_POINT *EC_KEY_get0_public_key(const EC_KEY *key);

  3)开始转换

size_t EC_POINT_point2oct(const EC_GROUP *group, const EC_POINT *p,
                          point_conversion_form_t form,
                          unsigned char *buf, size_t len, BN_CTX *ctx);

  group:椭圆曲线的参数,包含G a b 等信息,可以通过EC_KEY_get0_group(EVP_PKEY*pkey)拿到;

  p:公钥;

  form:数据存放格式。由以下三种格式,选择一种即可

typedef enum {
        /** the point is encoded as z||x, where the octet z specifies
         *  which solution of the quadratic equation y is  */
    POINT_CONVERSION_COMPRESSED = 2,
        /** the point is encoded as z||x||y, where z is the octet 0x04  */
    POINT_CONVERSION_UNCOMPRESSED = 4,
        /** the point is encoded as z||x||y, where the octet z specifies
         *  which solution of the quadratic equation y is  */
    POINT_CONVERSION_HYBRID = 6
} point_conversion_form_t;

  buf:转换成功后存放空间;

  len:空间大小;

  ctx:上下文,如果只做一次转换,可以填0;

收到8进制数据的密钥需要转回到EVP_PKEY*类型

  1)双方使用的是同样的椭圆曲线,然后生成密钥对EVP_PKEY*类型的pkey,通过这个pkey可以拿到当前椭圆曲线的参数group。

struct ec_key_st *EVP_PKEY_get0_EC_KEY(EVP_PKEY *pkey);
const EC_GROUP *EC_KEY_get0_group(const EC_KEY *key);

  2)将8进制数据转为POINT*类型

//创建EC_POINT类型的对象,后面要释放
EC_POINT* EC_POINT_new(const EC_GROUP*group);
//将公钥放在P中
int EC_POINT_oct2point(const EC_GROUP *group, EC_POINT *p,
                       const unsigned char *buf, size_t len, BN_CTX *ctx);

  此时公钥已经存放在p中

  3)创建一个椭圆曲线的低级、原始接口ec_key,椭圆曲线的公私钥和参数都存放在这个结构中。

EC_KEY *EC_KEY_new(void);
//将椭圆曲线的参数放在ec_key结构中
int EC_KEY_set_group(EC_KEY *key, const EC_GROUP *group);
//将公钥也放进去
int EC_KEY_set_public_key(EC_KEY *key, const EC_POINT *pub);

  以上完成后,对方的基点和公钥已经放在了EC_KEY中。

  4)将原始接口转为EVP_PKEY结构

EVP_PKEY *EVP_PKEY_new(void);
int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey, struct ec_key_st *key);

  生成共享密钥。下面演示了如何将一个私钥/公钥对(保存在pkey变量中)和某个对等体的公钥(保存在peerkey变量中)组合以导出共享秘密(保存在key变量中,其长度保存在keylen中)。

//使用pkey和ENGINE e中指定的算法分配公钥算法上下文
EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e);
//初始化密钥交换
int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx);
//设定对方公钥
int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer);
//计算的共享密钥放在key中
int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen);

完整代码,模拟双方协商密钥

.h

class XECDH
{
public:
    XECDH();
    //生成椭圆曲线密钥对
    bool CreateKey();
    //将自己生成pkey转为8进制字符串,准备发送给对方
    int GetPubKey(unsigned char *pubkey);
        //收到对方的数据后,转为evp_pkey
    EVP_PKEY* OctTokey(const  unsigned char *pubkey, int pubkey_size);
    //生成共享密钥
    int SharedKey(unsigned char *out, const unsigned char *ppkey, int key_size);

private:
    int nid;  //椭圆曲线的nid
    //密钥对存放
    EVP_PKEY*pkey = nullptr;
};

.cpp

XECDH::XECDH()
{
    nid = NID_secp256k1;
}

bool XECDH::CreateKey()
{
    //生成椭圆曲线的参数的上下文,用来生成对应的参数
    auto ctx=EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
    int re = EVP_PKEY_paramgen_init(ctx);
    if (re != 1)
    {
        ERR_print_errors_fp(stderr);
        EVP_PKEY_CTX_free(ctx);
        return false;
    }
    //选择椭圆曲线
    re=EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid);
    if (re != 1)
    {
        ERR_print_errors_fp(stderr);
        EVP_PKEY_CTX_free(ctx);
        return false;
    }
    //生成椭圆曲线参数存储到params
    EVP_PKEY*param = nullptr;
    re=EVP_PKEY_paramgen(ctx, &param);
    if (re != 1)
    {
        ERR_print_errors_fp(stderr);
        EVP_PKEY_CTX_free(ctx);
        return false;
    }
    EVP_PKEY_CTX_free(ctx);
    //生成密钥对
    //根据EC参数生成密钥对创建上下文
    auto kctx=EVP_PKEY_CTX_new(param,NULL);
    if (!kctx)
    {
        ERR_print_errors_fp(stderr);
        return false;
    }
    //生成密钥对的初始化
    re=EVP_PKEY_keygen_init(kctx);
    if (re != 1)
    {
        ERR_print_errors_fp(stderr);
        EVP_PKEY_CTX_free(kctx);
        return false;
    }
    re=EVP_PKEY_keygen(kctx, &pkey);
    EVP_PKEY_CTX_free(kctx);
    if (re != 1)
    {
        ERR_print_errors_fp(stderr);
        return false;
    }
    cout << "generkey success!" << endl;

    return true;
}

int XECDH::GetPubKey(unsigned char * pubkey)
{
    if (!pkey)
        return 0;
    //从pkey中拿到原始密钥
    auto key=EVP_PKEY_get0_EC_KEY(pkey);
    //拿到公钥,这是一个点
    auto pub = EC_KEY_get0_public_key(key);
    //将这个点转换成8进制
    int re=EC_POINT_point2oct(EC_KEY_get0_group(key), pub, 
POINT_CONVERSION_HYBRID, pubkey, 1024, 0); return re; } EVP_PKEY * XECDH::OctTokey(const unsigned char * pubkey, int pubkey_size) { //拿到当前椭圆曲线参数,里面有G,abp auto key = EVP_PKEY_get0_EC_KEY(pkey); auto group = EC_KEY_get0_group(key); //pubkey-->EC_POINT EC_POINT*p = EC_POINT_new(group);//公钥 EC_POINT_oct2point(group, p, pubkey, pubkey_size, 0);//将对方的公钥已经存储在P中 //椭圆曲线的低级、原始接口ec_key,椭圆曲线的公私钥和参数都存放在这个结构中 EC_KEY*ec_key = EC_KEY_new(); //将参数填充到ec_key中 EC_KEY_set_group(ec_key, group);//选择同样的椭圆曲线 EC_KEY_set_public_key(ec_key, p);//将公钥也填充到ec_key中 EC_POINT_free(p); //将原始接口转为高级接口evp_pkey EVP_PKEY *ppkey = EVP_PKEY_new(); EVP_PKEY_set1_EC_KEY(ppkey, ec_key); EC_KEY_free(ec_key); return ppkey; } int XECDH::SharedKey(unsigned char * out, const unsigned char * ppkey, int key_size) { //生成一个上下文 //函数使用pkey和ENGINE e中指定的算法分配公钥算法上下文。 auto ctx = EVP_PKEY_CTX_new(pkey,0); if (!ctx) { ERR_print_errors_fp(stderr); return 0; } //初始化密钥交换 int er=EVP_PKEY_derive_init(ctx); if (er != 1) { EVP_PKEY_CTX_free(ctx); ERR_print_errors_fp(stderr); return 0; } //设定对方公钥 bG 和私钥a int re=EVP_PKEY_derive_set_peer(ctx, OctTokey(ppkey, key_size)); if (re != 1) { EVP_PKEY_CTX_free(ctx); ERR_print_errors_fp(stderr); return 0; } //开始计算 size_t outsize = 1024; re=EVP_PKEY_derive(ctx, out, &outsize); if (re != 1) { EVP_PKEY_CTX_free(ctx); ERR_print_errors_fp(stderr); return 0; } EVP_PKEY_CTX_free(ctx); return outsize; }

main.cpp

int main()
{
    XECDH server;
    XECDH client;
        //存放sererv端生成公钥的8进制数据
    unsigned char spub[1024] = { 0 };
       //存放clinet 端生成公钥的8进制数据
    unsigned char cpub[1024] = { 0 };
        //server生成的共享密钥
    unsigned char serversharedkey[1024] = { 0 };
       //client生成的共享密钥
    unsigned char clintsharedkey[1024] = { 0 };
    //server生成一个密钥对
    server.CreateKey();
    //server将密钥对转为8进制数据
    int seroctlen = server.GetPubKey(spub);
        //////////////////////////////////////////////////
        ////////client端收到了server发送来的公钥///////
        /////////////////////////////////////////////////
    client.CreateKey();
    int clioctlen = client.GetPubKey(cpub);

    server.SharedKey(serversharedkey, cpub, clioctlen);
    client.SharedKey(clintsharedkey, spub, seroctlen);
    cout << "serevr生成的共享密钥:" << serversharedkey << endl;
    cout << "client生成共享密钥:" << clintsharedkey << endl;
    return 0;
}