介紹如何以 OpenSSL 的指令實作橢圓曲線 ECDH 金鑰交換,並參考 ECIES 加密架構,搭配 AES 演算法加密資料。
橢圓曲線迪菲-赫爾曼(Elliptic Curve Diffie–Hellman,簡稱 ECDH)金鑰交換方法是雙方利用由橢圓曲線密碼學建立的公鑰與私鑰對,在一個不安全的通道中,建立一組雙方共享的密鑰。
OpenSSL 支援的橢圓曲線很多,我們可以使用以下指令查詢:
# 查詢 OpenSSL 支援的橢圓曲線 openssl ecparam -list_curves
secp112r1 : SECG/WTLS curve over a 112 bit prime field secp112r2 : SECG curve over a 112 bit prime field secp128r1 : SECG curve over a 128 bit prime field secp128r2 : SECG curve over a 128 bit prime field secp160k1 : SECG curve over a 160 bit prime field secp160r1 : SECG curve over a 160 bit prime field secp160r2 : SECG/WTLS curve over a 160 bit prime field secp192k1 : SECG curve over a 192 bit prime field secp224k1 : SECG curve over a 224 bit prime field secp224r1 : NIST/SECG curve over a 224 bit prime field secp256k1 : SECG curve over a 256 bit prime field secp384r1 : NIST/SECG curve over a 384 bit prime field secp521r1 : NIST/SECG curve over a 521 bit prime field prime192v1: NIST/X9.62/SECG curve over a 192 bit prime field prime192v2: X9.62 curve over a 192 bit prime field prime192v3: X9.62 curve over a 192 bit prime field prime239v1: X9.62 curve over a 239 bit prime field prime239v2: X9.62 curve over a 239 bit prime field prime239v3: X9.62 curve over a 239 bit prime field prime256v1: X9.62/SECG curve over a 256 bit prime field sect113r1 : SECG curve over a 113 bit binary field sect113r2 : SECG curve over a 113 bit binary field sect131r1 : SECG/WTLS curve over a 131 bit binary field sect131r2 : SECG curve over a 131 bit binary field sect163k1 : NIST/SECG/WTLS curve over a 163 bit binary field sect163r1 : SECG curve over a 163 bit binary field sect163r2 : NIST/SECG curve over a 163 bit binary field sect193r1 : SECG curve over a 193 bit binary field sect193r2 : SECG curve over a 193 bit binary field sect233k1 : NIST/SECG/WTLS curve over a 233 bit binary field sect233r1 : NIST/SECG/WTLS curve over a 233 bit binary field sect239k1 : SECG curve over a 239 bit binary field sect283k1 : NIST/SECG curve over a 283 bit binary field sect283r1 : NIST/SECG curve over a 283 bit binary field sect409k1 : NIST/SECG curve over a 409 bit binary field sect409r1 : NIST/SECG curve over a 409 bit binary field sect571k1 : NIST/SECG curve over a 571 bit binary field sect571r1 : NIST/SECG curve over a 571 bit binary field c2pnb163v1: X9.62 curve over a 163 bit binary field c2pnb163v2: X9.62 curve over a 163 bit binary field c2pnb163v3: X9.62 curve over a 163 bit binary field c2pnb176v1: X9.62 curve over a 176 bit binary field c2tnb191v1: X9.62 curve over a 191 bit binary field c2tnb191v2: X9.62 curve over a 191 bit binary field c2tnb191v3: X9.62 curve over a 191 bit binary field c2pnb208w1: X9.62 curve over a 208 bit binary field c2tnb239v1: X9.62 curve over a 239 bit binary field c2tnb239v2: X9.62 curve over a 239 bit binary field c2tnb239v3: X9.62 curve over a 239 bit binary field c2pnb272w1: X9.62 curve over a 272 bit binary field c2pnb304w1: X9.62 curve over a 304 bit binary field c2tnb359v1: X9.62 curve over a 359 bit binary field c2pnb368w1: X9.62 curve over a 368 bit binary field c2tnb431r1: X9.62 curve over a 431 bit binary field wap-wsg-idm-ecid-wtls1: WTLS curve over a 113 bit binary field wap-wsg-idm-ecid-wtls3: NIST/SECG/WTLS curve over a 163 bit binary field wap-wsg-idm-ecid-wtls4: SECG curve over a 113 bit binary field wap-wsg-idm-ecid-wtls5: X9.62 curve over a 163 bit binary field wap-wsg-idm-ecid-wtls6: SECG/WTLS curve over a 112 bit prime field wap-wsg-idm-ecid-wtls7: SECG/WTLS curve over a 160 bit prime field wap-wsg-idm-ecid-wtls8: WTLS curve over a 112 bit prime field wap-wsg-idm-ecid-wtls9: WTLS curve over a 160 bit prime field wap-wsg-idm-ecid-wtls10: NIST/SECG/WTLS curve over a 233 bit binary field wap-wsg-idm-ecid-wtls11: NIST/SECG/WTLS curve over a 233 bit binary field wap-wsg-idm-ecid-wtls12: WTLS curve over a 224 bit prime field Oakley-EC2N-3: IPSec/IKE/Oakley curve #3 over a 155 bit binary field. Not suitable for ECDSA. Questionable extension field! Oakley-EC2N-4: IPSec/IKE/Oakley curve #4 over a 185 bit binary field. Not suitable for ECDSA. Questionable extension field! brainpoolP160r1: RFC 5639 curve over a 160 bit prime field brainpoolP160t1: RFC 5639 curve over a 160 bit prime field brainpoolP192r1: RFC 5639 curve over a 192 bit prime field brainpoolP192t1: RFC 5639 curve over a 192 bit prime field brainpoolP224r1: RFC 5639 curve over a 224 bit prime field brainpoolP224t1: RFC 5639 curve over a 224 bit prime field brainpoolP256r1: RFC 5639 curve over a 256 bit prime field brainpoolP256t1: RFC 5639 curve over a 256 bit prime field brainpoolP320r1: RFC 5639 curve over a 320 bit prime field brainpoolP320t1: RFC 5639 curve over a 320 bit prime field brainpoolP384r1: RFC 5639 curve over a 384 bit prime field brainpoolP384t1: RFC 5639 curve over a 384 bit prime field brainpoolP512r1: RFC 5639 curve over a 512 bit prime field brainpoolP512t1: RFC 5639 curve over a 512 bit prime field SM2 : SM2 curve over a 256 bit prime field
我們可以使用以下指令查看特定橢圓曲線的實際參數:
# 顯示 secp256k1 橢圓曲線參數 openssl ecparam -name secp256k1 -param_enc explicit -text -noout
EC-Parameters: (256 bit) Field Type: prime-field Prime: 00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff: ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:fe:ff: ff:fc:2f A: 0 B: 7 (0x7) Generator (uncompressed): 04:79:be:66:7e:f9:dc:bb:ac:55:a0:62:95:ce:87: 0b:07:02:9b:fc:db:2d:ce:28:d9:59:f2:81:5b:16: f8:17:98:48:3a:da:77:26:a3:c4:65:5d:a4:fb:fc: 0e:11:08:a8:fd:17:b4:48:a6:85:54:19:9c:47:d0: 8f:fb:10:d4:b8 Order: 00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff: ff:fe:ba:ae:dc:e6:af:48:a0:3b:bf:d2:5e:8c:d0: 36:41:41 Cofactor: 1 (0x1)
確認要採用的橢圓曲線之後,即可產生私鑰,由於我們要模擬兩方透過不安全的通道建立共享密鑰,所以這裡的操作會區分為甲方與乙方,事實上雙方的指令都是相同的。
# 產生 secp256k1 橢圓曲線私鑰(甲方) openssl ecparam -name secp256k1 -genkey -noout -out secp256k1_priv_key_1.pem
執行之後,產生的私鑰會儲存於 secp256k1_priv_key_1.pem
這個檔案中,我們可以查看私鑰的內容:
# 查看私鑰內容(甲方)
cat secp256k1_priv_key_1.pem
-----BEGIN EC PRIVATE KEY----- MHQCAQEEIJDuX0R1BbAx2PDe67bnUHMz4PCnNpMg6f41NOaoY0BpoAcGBSuBBAAK oUQDQgAEtzO2oa+wc884MGzZBn2tXciAW5AGlCLor00S6FnFFUPZHo9HEn4wAGTR hTmFn/KRvRdm8Lk3EEQ5rY+KJYRZ/g== -----END EC PRIVATE KEY-----
接著再從這把私鑰產生對應的公鑰:
# 從私鑰產生對應的公鑰(甲方) openssl ec -in secp256k1_priv_key_1.pem -pubout -out secp256k1_pub_key_1.pem
read EC key writing EC key
查看公鑰內容:
# 查看公鑰內容(甲方)
cat secp256k1_pub_key_1.pem
-----BEGIN PUBLIC KEY----- MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtzO2oa+wc884MGzZBn2tXciAW5AGlCLo r00S6FnFFUPZHo9HEn4wAGTRhTmFn/KRvRdm8Lk3EEQ5rY+KJYRZ/g== -----END PUBLIC KEY-----
乙方也按照同樣的方式,產生另外一對私鑰與公鑰:
# 產生 secp256k1 橢圓曲線私鑰(乙方) openssl ecparam -name secp256k1 -genkey -noout -out secp256k1_priv_key_2.pem # 從私鑰產生對應的公鑰(乙方) openssl ec -in secp256k1_priv_key_2.pem -pubout -out secp256k1_pub_key_2.pem
當雙方都以相同的橢圓曲線建立私鑰與公鑰之後,即可將各自的公鑰傳輸給對方,接著各自以對方的公鑰加上自己的私鑰產生共享密鑰:
# 產生共享密鑰(甲方) openssl pkeyutl -derive -inkey secp256k1_priv_key_1.pem \ -peerkey secp256k1_pub_key_2.pem -out secret_key_1.bin # 產生共享密鑰(乙方) openssl pkeyutl -derive -inkey secp256k1_priv_key_2.pem \ -peerkey secp256k1_pub_key_1.pem -out secret_key_2.bin
這樣甲方所產生的密鑰 secret_key_1.bin
與乙方所產生的密鑰 secret_key_2.bin
就會是相同的,我們可以查看其內容:
# 查看密鑰內容(甲方) od -x secret_key_1.bin
0000000 9f5a 1f06 4f5e f0bf cf1e ba0e 1730 cbb0 0000020 4cfc fa58 484b 3699 d13f 6f77 a291 bec7 0000040
# 查看密鑰內容(乙方) od -x secret_key_2.bin
0000000 9f5a 1f06 4f5e f0bf cf1e ba0e 1730 cbb0 0000020 4cfc fa58 484b 3699 d13f 6f77 a291 bec7 0000040
當雙方產生了相同的共享密鑰 secret_key_1.bin
與 secret_key_2.bin
之後,即可使用 AES 這類的對稱式加密方式來加密所有的訊息:
# 產生原始訊息檔案(甲方) echo 'top secret!!!' > message.txt # 使用密鑰檔案以 AES-256 加密(甲方) openssl enc -aes256 -pbkdf2 -pass file:./secret_key_1.bin \ -in message.txt -out message.txt.enc # 使用密鑰檔案以 AES-256 解密(乙方) openssl enc -aes256 -d -pbkdf2 -pass file:./secret_key_2.bin \ -in message.txt.enc -out message.txt.output
這裡使用 -pass 讀取密碼檔時,根據手冊說明,只會讀取密碼檔案中的第一行,所以可能對於二進位檔案會有讀取不完全的問題。
依據 ECIES 的架構,經過 ECDH 產生的密鑰要先經過 KDF 處理之後,才會作為 AES 加密的金鑰,跟這裡 OpenSSL 指令的實作方式有些差異,對於正式的環境,建議採用其他的標準的實作方式。
X25519 是將 Curve25519 作為 DH 函數並實作 ECDH 金鑰交換的方法,然而 X25519 並不屬於標準的橢圓曲線,所以在 OpenSSL 中的指令與標準的橢圓曲線不同,不能以 ecparams
或 ec
指令來進行 X25519 的操作,必須改用 genpkey
,以下是使用 OpenSSL 指令實作 X25519 的流程。
以下同樣將指令分為甲、乙兩方,首先產生 X25519 的私鑰與公鑰:
# 產生 X25519 的私鑰(甲方) openssl genpkey -algorithm X25519 -out x25519_priv_key_1.pem # 從私鑰產生對應的公鑰(甲方) openssl pkey -pubout -in x25519_priv_key_1.pem -out x25519_pub_key_1.pem # 產生 X25519 的私鑰(乙方) openssl genpkey -algorithm X25519 -out x25519_priv_key_2.pem # 從私鑰產生對應的公鑰(乙方) openssl pkey -pubout -in x25519_priv_key_2.pem -out x25519_pub_key_2.pem
雙方交換公鑰之後,即可用自己的私鑰加上對方的公鑰產生共享密鑰:
# 產生共享密鑰(甲方) openssl pkeyutl -derive -inkey x25519_priv_key_1.pem \ -peerkey x25519_pub_key_2.pem -out secret_key_1.bin # 產生共享密鑰(乙方) openssl pkeyutl -derive -inkey x25519_priv_key_2.pem \ -peerkey x25519_pub_key_1.pem -out secret_key_2.bin
產生共享密鑰之後,後續就可以按照一般的方式採用 AES-256 進行資料的加密了。