Linux

OpenSSL 指令實作橢圓曲線 ECDH 金鑰交換與 AES 加密教學與範例

介紹如何以 OpenSSL 的指令實作橢圓曲線 ECDH 金鑰交換,並參考 ECIES 加密架構,搭配 AES 演算法加密資料。

本篇文章屬於個人學習記錄,內容可能有誤,僅供參考,請勿用於正式環境。

OpenSSL 指令實作 ECDH 金鑰交換

橢圓曲線迪菲-赫爾曼(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.binsecret_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 指令的實作方式有些差異,對於正式的環境,建議採用其他的標準的實作方式。

OpenSSL 指令實作 X25519

X25519 是將 Curve25519 作為 DH 函數並實作 ECDH 金鑰交換的方法,然而 X25519 並不屬於標準的橢圓曲線,所以在 OpenSSL 中的指令與標準的橢圓曲線不同,不能以 ecparamsec 指令來進行 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 進行資料的加密了。

參考資料

Share
Published by
Office Guide

Recent Posts

Python 使用 PyAutoGUI 自動操作滑鼠與鍵盤

本篇介紹如何在 Python ...

1 年 ago

Ubuntu Linux 以 WireGuard 架設 VPN 伺服器教學與範例

本篇介紹如何在 Ubuntu ...

1 年 ago

Linux 網路設定 ip 指令用法教學與範例

本篇介紹如何在 Linux 系...

1 年 ago

Linux 以 Cryptsetup、LUKS 加密 USB 隨身碟教學與範例

介紹如何在 Linux 系統中...

1 年 ago