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