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, ¶m); 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; }