Go ECDH 算法的實現及應用
01 前言
這一篇,我們一起來了解一下 ECDH 算法。ECDH 算法是一種基於橢圓曲線加密的密鑰協商算法,被廣泛應用於網絡通信、數字簽名等領域。隨着互聯網的發展,數據安全問題越來越受到人們的關注,國內也頒佈了《中華人民共和國個人信息保護法》以及對外出口有歐盟的 GDPR 等。而 ECDH 算法作爲一種安全性高、計算效率高的加密算法,受到了越來越多的關注和應用。這篇文章一起來了解一下 ECDH 算法的基本原理、實現方法以及應用場景。
02 橢圓曲線 ECC 算法
我們先來了解下什麼是 ECC 算法。ECC(Elliptic Curve Cryptography) 是一種基於橢圓曲線的密碼學算法,用於實現加密、數據簽名、密鑰交換等安全通信協議。與傳統的 RSA 算法相比,ECC 提供了相同安全性的情況下,使用更短的密鑰長度和更高的計算效率。
橢圓曲線是一個定義在有限域上的曲線,它的數學特性使得橢圓曲線上的運算具有一些重要的性質,包括離散對數問題。這些性質爲 ECC 算法提供了安全性基礎。
ECC 算法的主要應用場景之一就是密鑰交換 (ECDH)。爲了解決集中式管理密鑰的缺點,有人提出了 Diffie-Hellman 密鑰交換的方法。在 Diffie-Hellman 密鑰交換中,加密通信雙方需要交換一些信息,而這個過程是可以防止竊聽者攻擊的。參與方使用橢圓曲線上的點進行公鑰交換,協商出共性密鑰,用於對稱加密通信。將 DH 算法和 ECC 公鑰算法結合形成了 ECDH 算法,這是一種解決密鑰交換問題的一種很常見的解決方案。
03 ECDH 算法
在 ECDH 算法中,參與方使用橢圓曲線上的點來生成公鑰和私鑰對。橢圓曲線提供了一種安全的數學基礎,使得生成的公鑰無法被輕易導出私鑰。
下面瞭解一下兩個參與方之間的密鑰交換過程:
-
參與方 A 和 B 事先協商好使用的橢圓曲線參數。
-
參與方 A 生成自己的公私鑰對。
-
參與方 B 也生成自己的公私鑰對。
-
參與方 A 將自己的公鑰發送給參與方 B,參與方 B 將自己的公鑰也發送給參與方 A。
-
參與方 A 使用自己的私鑰和參與方 B 的公鑰進行計算,得到共享密鑰。
-
參與方 B 使用自己的私鑰和參與方 A 的公鑰進行計算,也得到相同的共享密鑰。
由於整個過程中只涉及到參與雙方的公鑰,並不需要用到私鑰,就能得到相同的共享密鑰。即使攻擊者截獲了參與雙方的公鑰,也無法輕易推導出私鑰或者共享密鑰,這就爲加密通信提供了重要的基礎。
下面,我們以客戶端 (app/web) 向服務端 (server) 傳入加密參數爲例,方便大家瞭解。
04 算法實現
由上面所講的知識,我們瞭解到了需要爲參與方生成公私鑰對,並根據公私鑰對生成共享密鑰。因此,我們可以生成以下接口去實現
// ECDH The main interface for ECDH key exchange.
type ECDH interface {
// GenerateKey 生成公私鑰對
GenerateKey(io.Reader) (crypto.PrivateKey, crypto.PublicKey, error)
// Marshal 將公鑰轉換爲字節
Marshal(crypto.PublicKey) []byte
// Unmarshal 將字節轉換爲公鑰
Unmarshal([]byte) (crypto.PublicKey, bool)
// GenerateSharedSecret 生成共享密鑰
GenerateSharedSecret(crypto.PrivateKey, crypto.PublicKey) ([]byte, error)
// HexEncodePublicKeyToString 將公鑰鑰轉換爲hex字符串
HexEncodePublicKeyToString(crypto.PublicKey) string
// HexEncodePrivateKeyToString 將私鑰轉換爲hex字符串
HexEncodePrivateKeyToString(crypto.PrivateKey) string
}
代碼實現如下:
// ecdh The main implementation of ECDH.
type ecdh struct {
ECDH
curve elliptic.Curve
}
// ecdhPublicKey The public key for ecdh.
type ecdhPublicKey struct {
elliptic.Curve
X, Y *big.Int
}
// ecdhPrivateKey The private key for ecdh.
type ecdhPrivateKey struct {
D []byte
}
// NewECDH creates a new instance of ECDH with the given elliptic.Curve curve
// to use as the elliptical curve for elliptical curve diffie-hellman.
func NewECDH(curve elliptic.Curve) ECDH {
return &ecdh{
curve: curve,
}
}
// GenerateKey generates a public and private key pair.
func (e *ecdh) GenerateKey(rand io.Reader) (crypto.PrivateKey, crypto.PublicKey, error) {
var d []byte
var x, y *big.Int
var privateKey *ecdhPrivateKey
var publicKey *ecdhPublicKey
var err error
d, x, y, err = elliptic.GenerateKey(e.curve, rand)
if err != nil {
return nil, nil, err
}
privateKey = &ecdhPrivateKey{
D: d,
}
publicKey = &ecdhPublicKey{
Curve: e.curve,
X: x,
Y: y,
}
return privateKey, publicKey, nil
}
// Marshal converts a public key to bytes.
func (e *ecdh) Marshal(publicKey crypto.PublicKey) []byte {
key := publicKey.(*ecdhPublicKey)
return elliptic.Marshal(e.curve, key.X, key.Y)
}
// Unmarshal converts bytes to a public key.
func (e *ecdh) Unmarshal(data []byte) (crypto.PublicKey, bool) {
var key *ecdhPublicKey
var x, y *big.Int
x, y = elliptic.Unmarshal(e.curve, data)
if x == nil || y == nil {
return key, false
}
key = &ecdhPublicKey{
Curve: e.curve,
X: x,
Y: y,
}
return key, true
}
// GenerateSharedSecret takes in a public key and a private key
// and generates a shared secret.
//
// RFC5903 Section 9 states we should only return x.
func (e *ecdh) GenerateSharedSecret(privateKey crypto.PrivateKey, publicKey crypto.PublicKey) ([]byte, error) {
private := privateKey.(*ecdhPrivateKey)
public := publicKey.(*ecdhPublicKey)
x, _ := e.curve.ScalarMult(public.X, public.Y, private.D)
return x.Bytes(), nil
}
// HexEncodePublicKeyToString 將公鑰鑰轉換爲hex字符串
func (e *ecdh) HexEncodePublicKeyToString(publicKey crypto.PublicKey) string {
return hex.EncodeToString(e.Marshal(publicKey))
}
// HexEncodePrivateKeyToString 將私鑰轉換爲hex字符串
func (e *ecdh) HexEncodePrivateKeyToString(privateKey crypto.PrivateKey) string {
return hex.EncodeToString(privateKey.(*ecdhPrivateKey).D)
}
爲了方便使用,同樣提供了獲取 ecdh 密鑰的封裝:
// GetECDHSecret 使用自己的私鑰和別人的公鑰(hex),協商密鑰
func GetECDHSecret(privateKey, publicKey string) (string, error) {
e := NewECDH(elliptic.P256())
privateKeyByte, err := hex.DecodeString(privateKey)
if err != nil {
return "", err
}
servicePrivateKey := &ecdhPrivateKey{
D: privateKeyByte,
}
publicKeyByte, err := hex.DecodeString(publicKey)
if err != nil {
return "", err
}
clientPublicKey, ok := e.Unmarshal(publicKeyByte)
if !ok {
return "", errors.New("unmarshal public key error")
}
secret, err := e.GenerateSharedSecret(servicePrivateKey, clientPublicKey)
if err != nil {
return "", err
}
if len(secret) == 0 {
return "", errors.New("secret is empty")
}
return fmt.Sprintf("%064x", secret), nil
}
其單元測試如下:
func testECDH(e ECDH, t testing.TB) {
var privKey1, privKey2 crypto.PrivateKey
var pubKey1, pubKey2 crypto.PublicKey
var pubKey1Buf, pubKey2Buf []byte
var err error
var ok bool
var secret1, secret2 []byte
privKey1, pubKey1, err = e.GenerateKey(rand.Reader)
if err != nil {
t.Error(err)
}
privKey2, pubKey2, err = e.GenerateKey(rand.Reader)
if err != nil {
t.Error(err)
}
pubKey1Buf = e.Marshal(pubKey1)
pubKey2Buf = e.Marshal(pubKey2)
pubKey1, ok = e.Unmarshal(pubKey1Buf)
if !ok {
t.Fatalf("Unmarshal does not work")
}
pubKey2, ok = e.Unmarshal(pubKey2Buf)
if !ok {
t.Fatalf("Unmarshal does not work")
}
secret1, err = e.GenerateSharedSecret(privKey1, pubKey2)
if err != nil {
t.Error(err)
}
secret2, err = e.GenerateSharedSecret(privKey2, pubKey1)
if err != nil {
t.Error(err)
}
if !bytes.Equal(secret1, secret2) {
t.Fatalf("The two shared keys: %d, %d do not match", secret1, secret2)
}
}
func TestGetECDHSecret(t *testing.T) {
publicKey := "040046627e2dbc0ec479fba60738831aba45a9c4bb132bbbb13e989efdd7b77e3a5e860c89710899d26e53e4b7e778a1a60663c5b9ca2b5c9772818d299fb43b8e"
e := NewECDH(elliptic.P256())
privKey, pubKey, err := e.GenerateKey(rand.Reader)
if err != nil {
return
}
fmt.Println(e.HexEncodePublicKeyToString(pubKey))
privateKey := e.HexEncodePrivateKeyToString(privKey)
fmt.Println(privateKey)
secret, err := GetECDHSecret(privateKey, publicKey)
if err != nil {
return
}
keyByte, err := hex.DecodeString(secret)
encryptString, err := AesCBCEncryptString(string(keyByte), "hello world")
if err != nil {
return
}
fmt.Println(encryptString)
}
05 總結
ECDH 算法是一種基於橢圓曲線加密的密鑰協商算法,具有以下優點和缺點:
優點:
1. 安全性高:ECDH 算法基於橢圓曲線離散對數問題,目前沒有有效的攻擊方法。
2. 計算效率高:相比傳統的 DH 算法,ECDH 算法在相同安全性下,計算量更小,速度更快。
3. 密鑰長度短:ECDH 算法的密鑰長度比傳統 DH 算法短,可以減少傳輸和存儲的成本。
缺點:
1. 實現複雜:相比傳統 DH 算法,ECDH 算法的實現較爲複雜,需要選擇合適的橢圓曲線和基點。
2. 兼容性差:ECDH 算法需要雙方都支持橢圓曲線加密,如果有一方不支持,則無法使用該算法。
3. 安全性受到橢圓曲線選擇的影響:如果選擇的橢圓曲線存在漏洞或被攻擊者掌握,則會影響 ECDH 算法的安全性。
適用場景:
1. 安全性要求高的場景:ECDH 算法適用於對安全性要求較高的場景,如網絡通信、數字簽名等。
2. 計算資源有限的場景:ECDH 算法相比傳統 DH 算法,計算量更小,適用於計算資源有限的場景。
3. 密鑰長度要求短的場景:ECDH 算法的密鑰長度比傳統 DH 算法短,適用於密鑰長度要求短的場景,如移動設備等。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/LB-PJLtN7nXKtl5VrIC33A