介紹如何在 C 語言中使用 OpenSSL 實作橢圓曲線 ECDH 金鑰交換。
產生橢圓曲線金鑰對
在實作橢圓曲線 ECDH 金鑰交換之前,要先產生兩對橢圓曲線的金鑰對,以下是以 C 語言程式的方式來產生金鑰的範例:
#include <stdlib.h> #include <openssl/evp.h> #include <openssl/ec.h> #include <openssl/pem.h> // 產生並儲存橢圓曲線金鑰 void generateECKey(const char *privKeyFile, const char *pubKeyFile) { // 產生 secp256k1 金鑰對 EVP_PKEY *ecKey = EVP_EC_gen("secp256k1"); // 輸出公鑰與私鑰資訊 //EVP_PKEY_print_public_fp(stdout, ecKey, 0, NULL); EVP_PKEY_print_private_fp(stdout, ecKey, 0, NULL); // 將私鑰寫入 privKeyFile 中 BIO *bioPrivKey = BIO_new_file(privKeyFile, "w"); PEM_write_bio_PrivateKey(bioPrivKey, ecKey, NULL, NULL, 0, NULL, NULL); BIO_free(bioPrivKey); // 將公鑰寫入 pubKeyFile 中 BIO *bioPubKey = BIO_new_file(pubKeyFile, "w"); PEM_write_bio_PUBKEY(bioPubKey, ecKey); BIO_free(bioPubKey); // 釋放橢圓曲線金鑰空間 EVP_PKEY_free(ecKey); } int main() { // 產生兩組橢圓曲線金鑰對 generateECKey("ec_private_key1.pem", "ec_public_key1.pem"); generateECKey("ec_private_key2.pem", "ec_public_key2.pem"); return EXIT_SUCCESS; }
這裡為了方便初學者閱讀,所以實作上省略了所有的錯誤檢查,實務上每一條函數呼叫都要檢查傳回值是否正確。
將這段程式碼儲存至 gen_ec_keys.c
,並用以下指令進行編譯:
# 編譯金鑰產生程式 gcc -o gen_ec_keys gen_ec_keys.c -lcrypto
編譯之後,會產生 gen_ec_keys
這個執行檔,執行後即可產生兩組橢圓曲線的金鑰對:
# 執行金鑰產生程式
./gen_ec_keys
Private-Key: (256 bit) priv: 71:0a:27:9e:11:58:cd:51:e6:7a:83:1b:18:0f:d2: cc:03:29:7b:80:61:43:23:6f:20:ea:b7:17:44:75: 26:f5 pub: 04:d1:7f:73:c6:31:14:bf:5e:01:52:14:15:5f:7e: bc:12:49:cf:ce:7c:e3:b0:57:31:43:25:15:27:6a: f7:9d:96:9e:06:67:be:78:dd:6e:c3:58:05:73:ba: 92:c8:14:09:b5:82:7c:08:5c:91:ee:82:10:bd:b0: 23:2c:59:c9:64 ASN1 OID: secp256k1 Private-Key: (256 bit) priv: f3:d9:9f:d0:b7:c5:df:24:0d:cb:30:c9:b7:b6:7c: c9:08:ee:4d:71:eb:d4:65:bc:93:a8:08:c7:04:e2: 68:f6 pub: 04:b8:b2:71:6f:58:48:2a:97:61:04:86:8b:2d:98: ca:b8:ee:af:eb:65:90:ac:3a:39:69:4d:69:89:f1: 92:e3:2e:2a:64:5e:9a:99:71:bc:46:50:fd:bc:de: c9:29:31:b7:d8:30:a6:bc:37:e9:50:87:a0:4c:fd: 9a:7e:3d:99:22 ASN1 OID: secp256k1
除了使用 C 語言之外,我們也可以參考 OpenSSL 指令實作橢圓曲線 ECDH 金鑰交換與 AES 加密教學與範例,以 OpenSSL 指令來產生橢圓曲線金鑰對。
橢圓曲線 ECDH 金鑰交換
當雙方都準備好自己的橢圓曲線金鑰對,並將公鑰交給對方之後,就可以透過 ECDH 金鑰交換的方式,計算出共享密鑰,以下是 C 語言實作的範例:
#include <stdlib.h> #include <openssl/evp.h> #include <openssl/ec.h> #include <openssl/pem.h> // 橢圓曲線 ECDH 金鑰交換 void ecdh(const char *privKeyFile, const char *peerPubKeyFile, const char *secretFile) { // 讀取己方私鑰 EVP_PKEY *privKey; BIO *bioPrivKey = BIO_new_file(privKeyFile, "r"); PEM_read_bio_PrivateKey(bioPrivKey, &privKey, NULL, NULL); BIO_free(bioPrivKey); // 讀取對方公鑰 EVP_PKEY *peerPubKey; BIO *bioPubKey = BIO_new_file(peerPubKeyFile, "r"); PEM_read_bio_PUBKEY(bioPubKey, &peerPubKey, NULL, NULL); BIO_free(bioPrivKey); // 建立 context EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(NULL, privKey, NULL); EVP_PKEY_derive_init(ctx); EVP_PKEY_derive_set_peer(ctx, peerPubKey); // 取得共享密鑰長度 size_t secretLen; EVP_PKEY_derive(ctx, NULL, &secretLen); // 計算共享密鑰 unsigned char secret[secretLen]; EVP_PKEY_derive(ctx, secret, &secretLen); // 將共享密鑰儲存至檔案 BIO *bioSecret = BIO_new_file(secretFile, "wb"); BIO_write(bioSecret, secret, secretLen); BIO_free(bioSecret); // 釋放 context 空間 EVP_PKEY_CTX_free(ctx); } int main() { // 橢圓曲線 ECDH 金鑰交換 ecdh("ec_private_key1.pem", "ec_public_key2.pem", "ec_shared_secret1.bin"); ecdh("ec_private_key2.pem", "ec_public_key1.pem", "ec_shared_secret2.bin"); return EXIT_SUCCESS; }
將這段程式碼儲存至 ecdh.c
,並用以下指令進行編譯:
# 編譯金鑰產生程式 gcc -o ecdh ecdh.c -lcrypto
編譯之後,會產生 ecdh
這個執行檔,執行後就會模擬雙方各拿自己的私鑰加上對方的公鑰,計算共享密鑰:
# 執行金鑰產生程式
./ecdh
產生的密鑰會存放於 ec_shared_secret1.bin
與 ec_shared_secret2.bin
,理論上這兩組密鑰會是一模一樣的。
我們同時可以使用 OpenSSL 指令以同樣的方式計算共享密鑰:
# 產生共享密鑰 openssl pkeyutl -derive -inkey ec_private_key1.pem \ -peerkey ec_public_key2.pem -out ec_shared_secret1_cmd.bin openssl pkeyutl -derive -inkey ec_private_key2.pem \ -peerkey ec_public_key1.pem -out ec_shared_secret2_cmd.bin
最後驗證所有的共享密鑰是否相同:
# 驗證共享密鑰一致性
sha1sum ec_shared_secret*.bin
a62d3612640007905ac21e827631a6bce9ab6bcd ec_shared_secret1.bin a62d3612640007905ac21e827631a6bce9ab6bcd ec_shared_secret1_cmd.bin a62d3612640007905ac21e827631a6bce9ab6bcd ec_shared_secret2.bin a62d3612640007905ac21e827631a6bce9ab6bcd ec_shared_secret2_cmd.bin