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 算法中,參與方使用橢圓曲線上的點來生成公鑰和私鑰對。橢圓曲線提供了一種安全的數學基礎,使得生成的公鑰無法被輕易導出私鑰。

下面瞭解一下兩個參與方之間的密鑰交換過程:

  1. 參與方 A 和 B 事先協商好使用的橢圓曲線參數。

  2. 參與方 A 生成自己的公私鑰對。

  3. 參與方 B 也生成自己的公私鑰對。

  4. 參與方 A 將自己的公鑰發送給參與方 B,參與方 B 將自己的公鑰也發送給參與方 A。

  5. 參與方 A 使用自己的私鑰和參與方 B 的公鑰進行計算,得到共享密鑰。

  6. 參與方 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