【Golang】ECDSA 公開鍵から DH 鍵交換で共通鍵を作成する（ディフィー・ヘルマン鍵共有）

> Go 言語（以下 golang）で、[共通鍵暗号](https://ja.wikipedia.org/wiki/%E5%85%B1%E9%80%9A%E9%8D%B5%E6%9A%97%E5%8F%B7)用の**共通の秘密鍵を、お互いの公開鍵から作成したい**。

つまり「相手の公開鍵」と「私の秘密鍵」をゴニョゴニョして作成された鍵が、相手も同じように「私の公開鍵」と「相手の秘密鍵」でゴニョゴニョして作成された鍵と同じであれば、2 人の共通の秘密鍵として利用できるということです。

[GPG](https://ja.wikipedia.org/wiki/GNU_Privacy_Guard) で作成できる鍵で、[GitHub に登録できるアルゴリズム](https://docs.github.com/ja/authentication/managing-commit-signature-verification/checking-for-existing-gpg-keys)（RSA, ElGamal, DSA, ECDH, <font color="orange">ECDSA</font>, EdDSA）のうち、電子署名用アルゴリズム **ECDSA（楕円曲線電子署名アルゴリズム） の公開鍵から Golang で作成したい**。ついでに Golang で鍵のペアの作成もしたいのです。

- [ECDSA](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9ADSA) 公開鍵から [DH 鍵交換](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%A3%E3%83%95%E3%82%A3%E3%83%BC%E3%83%BB%E3%83%98%E3%83%AB%E3%83%9E%E3%83%B3%E9%8D%B5%E5%85%B1%E6%9C%89)で共通鍵を作成する ← いまここ
- [ECDH 公開鍵から DH 鍵交換で共通鍵を作成する](https://qiita.com/KEINOS/items/ef513bbb629f46d548a3) @ Qiita

:::note info
この記事は、[ECDSA](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9ADSA) 署名鍵でも DH 鍵交換により共通鍵を作成できるという [PoC](https://ja.wikipedia.org/wiki/%E6%A6%82%E5%BF%B5%E5%AE%9F%E8%A8%BC)（概念実証）です。最新のデジタル署名標準（FIPS 186-5）では <font color=darkorange>**ECDSA の鍵から別の鍵を生成してはいけない**</font>とされています。

> ECDSA is the elliptic curve analog of DSA. ECDSA keys **shall not** be used for any other purpose (e.g., key establishment).
> （ "The Elliptic Curve Digital Signature Algorithm (ECDSA)" | 6 章 | [FIPS FIPS 186-5](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf) より）

昔から、[DSA 同様、ECDSA には何かといわく付き](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9ADSA#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3)なので、署名鍵の生成で EdDSA, ECDA と Ed25519 で迷ったら、迷わず Ed25519 を選びましょう。

:::

## TL; DR <sub><sup>（今北産業）</sup></sub>

```go:AliceとBobの事前合意
import "crypto/elliptic"

// Alice と Bob の事前合意。共通パラメーターとして NIST P256 の曲線を使用する、とする。
// 選択肢は: P224, P256, P384, P521
paramCommon := elliptic.P256()
```
```go:各々の公開鍵暗号ペア鍵作成
import "crypto/ecdsa"

// --------------------------------------------------------------
//  Alice の公開鍵暗号のペア鍵作成
// --------------------------------------------------------------
alicePriv, err := ecdsa.GenerateKey(paramCommon, rand.Reader)
PanicOnError(err)

alicePub := alicePriv.PublicKey

// 作成例 （作成するごとに変わる）:
// Private key (Alice): f963265a0effb618cc133346c3c083d022fcac1229baa524561002250a7b2e65
// Public key (Alice) : X=f07591a19733607b4ea596e5ff0f10848cb069d7751e7347c216808ace86e4cd, Y=e921b0c68b28757dbd042323645c183d0a38cac392b86720bd957810656a5d57

// --------------------------------------------------------------
//  Bob の公開鍵暗号のペア鍵作成
// --------------------------------------------------------------
bobPriv, err := ecdsa.GenerateKey(paramCommon, rand.Reader)
PanicOnError(err)

bobPub := bobPriv.PublicKey

// 作成例 （作成するごとに変わる）:
// Private key (Bob): 76fea912bb5a7fa991348bb8cd8a2264c24831b2c98a9400dbf234a0ccc664a1
// Public key (Bob) : X=f7ad06524a8aa5e05e53114a0b09ede7928ea01f7c2b1912d67626c56e87ec03, Y=a010b028c007f89c8802833e198a87cc3e70b7cf771a630af2f4e20faf7a978e)
```
```go:各々の共通秘密鍵の生成
// Alice が、Bob の公開鍵から Alice の秘密鍵で 2 者の共通秘密鍵を作成
aliceSharedRaw, _ := bobPub.Curve.ScalarMult(bobPub.X, bobPub.Y, alicePriv.D.Bytes())
aliceShared := sha3.Sum256(aliceSharedRaw.Bytes()) // 扱いやすいようにバイトデータをハッシュ化

// Bob が、Alice の公開鍵から Bob の秘密鍵で 2 者の共通秘密鍵を作成
bobSharedRaw, _ := alicePub.Curve.ScalarMult(alicePub.X, alicePub.Y, bobPriv.D.Bytes())
bobShared := sha3.Sum256(bobSharedRaw.Bytes()) // 扱いやすいようにバイトデータをハッシュ化

// 両者が各々算出した共通秘密鍵
// aliceShared: c572eb446818b1883a5a0986e7ed190f745065567459fd5f668b4d13dc6290b9
// bobShared  : c572eb446818b1883a5a0986e7ed190f745065567459fd5f668b4d13dc6290b9
```

<details><summary>全ソースコードを見る</summary>

```go
package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"

	"golang.org/x/crypto/sha3"
)

func main() {
	// --------------------------------------------------------------------
	// Alice と Bob の事前合意。共通パラメーターとして NIST P256 の曲線を使用するとする。
	// 選択肢は: P224, P256, P384, P521
	// --------------------------------------------------------------------
	paramCommon := elliptic.P256()

	fmt.Printf("-- 共通パラメーター情報（ECC Parameters) --\n")
	fmt.Printf(" Name: %s\n", paramCommon.Params().Name)         // 使用される曲線の名前
	fmt.Printf(" P : %x\n", paramCommon.Params().P)              // フィールドの位数
	fmt.Printf(" N : %x\n", paramCommon.Params().N)              // 基点の位数
	fmt.Printf(" B : %x\n", paramCommon.Params().B)              // 曲線の方程式の定数
	fmt.Printf(" Gx: %x\n", paramCommon.Params().Gx)             // 基点の X 座標
	fmt.Printf(" Gy: %x\n", paramCommon.Params().Gy)             // 基点の Y 座標
	fmt.Printf(" Bitsize: %x\n\n", paramCommon.Params().BitSize) // フィールドのサイズ

	// --------------------------------------------------------------------
	//  公開鍵暗号のペア鍵作成
	// --------------------------------------------------------------------

	// Alice の公開鍵暗号ペア鍵作成
	alicePriv, err := ecdsa.GenerateKey(paramCommon, rand.Reader)
	PanicOnError(err)

	alicePub := alicePriv.PublicKey

	fmt.Printf("-- Alice のペア鍵 --\n")
	fmt.Printf("Private key (Alice): %x\n", alicePriv.D)
	fmt.Printf("Public key (Alice) : X=%x, Y=%x\n\n", alicePub.X, alicePub.Y)

	// Bob の公開鍵暗号ペア鍵作成
	bobPriv, err := ecdsa.GenerateKey(paramCommon, rand.Reader)
	PanicOnError(err)

	bobPub := bobPriv.PublicKey

	fmt.Printf("-- Bob のペア鍵 --\n")
	fmt.Printf("Private key (Bob): %x\n", bobPriv.D)
	fmt.Printf("Public key (Bob) : X=%x, Y=%x)\n\n", bobPub.X, bobPub.Y)

	// --------------------------------------------------------------------
	//  共通秘密鍵の作成
	// --------------------------------------------------------------------

	// Alice が、Bob の公開鍵から Alice の秘密鍵で 2 者の共通秘密鍵を作成
	aliceSharedRaw, _ := bobPub.Curve.ScalarMult(bobPub.X, bobPub.Y, alicePriv.D.Bytes())
	aliceShared := sha3.Sum256(aliceSharedRaw.Bytes()) // 扱いやすいようにバイトデータをハッシュ化

	// Bob が、Alice の公開鍵から Bob の秘密鍵で 2 者の共通秘密鍵を作成
	bobSharedRaw, _ := alicePub.Curve.ScalarMult(alicePub.X, alicePub.Y, bobPriv.D.Bytes())
	bobShared := sha3.Sum256(bobSharedRaw.Bytes()) // 扱いやすいようにバイトデータをハッシュ化

	// 共通秘密鍵の確認
	fmt.Printf("-- 両者の各々算出した共通秘密鍵 --\n")
	fmt.Printf("Shared key (Alice): %x\n", aliceShared)
	fmt.Printf("Shared key (Bob)  : %x\n", bobShared)
}

func PanicOnError(err error) {
	if err != nil {
		panic(err)
	}
}
```

</details>

- [オンラインで動作を見る](https://go.dev/play/p/ZoRycotoCcR) @ GoPlayground

## 参考文献

- [Standard library/crypto/ecdh](https://pkg.go.dev/crypto/ecdh) @ godoc
- [Standard library/crypto/elliptic](https://pkg.go.dev/crypto/elliptic) @ godoc
- [楕円曲線暗号](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9A%E6%9A%97%E5%8F%B7) @ Wikipedia
- [楕円曲線ディフィー・ヘルマン鍵共有](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9A%E3%83%87%E3%82%A3%E3%83%95%E3%82%A3%E3%83%BC%E3%83%BB%E3%83%98%E3%83%AB%E3%83%9E%E3%83%B3%E9%8D%B5%E5%85%B1%E6%9C%89) @ Wikipedia
- [Diffie-Hellman鍵交換](https://okumuralab.org/~okumura/misc/211210.html) @ okumuralab.org (三重大学 奥村名誉教授 雑記ブログ)
- Issue #52221 [crypto/ecdh: new package](https://github.com/golang/go/issues/52221) | Golang @ GitHub
- [Golang ECDH](https://asecuritysite.com/encryption/goecdh) @ asecuritysite.com
- [Efficient and Secure Elliptic Curve Cryptography Implementation of Curve P-256](https://csrc.nist.gov/csrc/media/events/workshop-on-elliptic-curve-cryptography-standards/documents/papers/session6-adalier-mehmet.pdf) @ NIST (PDF)
- [公開鍵暗号を巡る新しい動き：RSAから楕円曲線暗号へ](https://www.imes.boj.or.jp/research/papers/japanese/kk32-3-2.pdf) @ 日本銀行金融研究所