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

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

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

[GPG（GnuPG）](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, <font color="darkorange">ECDH</font>, ECDSA, EdDSA）のうち、 **[ECDH](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)（楕円曲線ディフィー・ヘルマン鍵共有）を Golang で利用したい**。ついでに Golang で ECDH の鍵ペアも作成したい。

- [ECDSA 公開鍵から DH 鍵交換で共通鍵を作成する](https://qiita.com/KEINOS/items/db51a1a6e775a03a650f) @ Qiita
- ECDH 公開鍵から DH 鍵交換で共通鍵を作成する ← いまここ

:::note warn
ここでは、**相手の公開鍵は安全な方法で取得していると仮定**します。

<sup>というのも、DH 鍵交換であっても[中間者攻撃](https://ja.wikipedia.org/wiki/%E4%B8%AD%E9%96%93%E8%80%85%E6%94%BB%E6%92%83)はありえるからです。例えば、取得した相手の鍵の URL が攻撃者に乗っ取られており、相手も、自分も実は攻撃者と共通秘密鍵を作っていたというケースです。</sup>

<sup>「本当に相手の鍵であるか」は、直面して（物理接触で）公開鍵を事前に交換しておくのが理想です。しかし、オンラインの場合は、<font color=darkorange>**複数のソースから総合的に判断することが重要**</font>です。[CA](https://ja.wikipedia.org/wiki/%E8%AA%8D%E8%A8%BC%E5%B1%80) を利用したり、公開鍵を [OpenPGP のキーサーバー](keys.openpgp.org) や [GitHub に登録](https://github.com/KEINOS.gpg) しておき、[Keyoxide](https://docs.keyoxide.org/) や [Keybase](https://keybase.io/) などの活用も検討すると良いでしょう。</sup>
:::

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

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

// Alice と Bob が事前に合意した共通パラメーター。この例では NIST P384 の
// 曲線を使用する、とする。
// 選択肢は: P256, P384, P521, X25519
// (悩んだら X25519 が良い。文末の「P521 vs X25519？」参照）
paramCommon := ecdh.P384()
```

```go:各々の公開鍵暗号ペア鍵作成
import "crypto/rand"

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

fmt.Printf("Alice Priv: %x\n", alicePrivKey.Bytes())

alicePubKey := alicePrivKey.PublicKey()
fmt.Printf("Alice Pub: %x\n", alicePubKey.Bytes())

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

fmt.Printf("Bob Priv: %x\n", bobPrivKey.Bytes())

bobPubKey := bobPrivKey.PublicKey()
fmt.Printf("Bob Pub: %x\n", bobPubKey.Bytes())

// 作成例 （作成するごとに変わる）:
// Alice Priv: 0fd7557d2dfd4dedc0b659ac8416333f97900cb1dab53720a66c7adea2b36f88e5a3f3eef490f9b48e66a7d8112c6f3d
// Alice Pub: 044806f4f84614189cc049e282696661516309f75ef20fbaf0cebd2fd4fc2bc2b3f19a25f550a2baefc350861ba470e0fee36478986571e46ba98862a7b2c55b904fc97079b899ec1195052f309d693598ea1ec13d53a6540b8ce640403f6a899d
// Bob Priv: 1ab739460d9e9b18fa5f5494c7667d4f929bb50e79144a6e5dc9549134fe7e1b4ac3eb0af926ab957c019ec16ba1be08
// Bob Pub: 04b742944773613e81b8b84c940e0a6b70d5b1ce9f453311a289bddff8812ed463fed7d246c5915bf777f209a2057ad27afe2dd2cd149d52f499bad1f085f92c590c5004878cdc621056010906b5e3ebf8b7fe3da3a46efee8bd1c09c44d7cf345
```

```go:各々の共通秘密鍵の生成
// Alice が、自分の秘密鍵と相手（Bob）の公開鍵で 2 者間の共通秘密鍵（aliceSecret）
// を作成。
aliceSecret, err := alicePrivKey.ECDH(bobPubKey)
panicOnErr(err)
fmt.Printf("Alice's Shared Secret: %x\n", aliceSecret)

// Bob が、自分の秘密鍵と相手（Alice）の公開鍵で 2 者間の共通秘密鍵（aliceSecret）
// を作成。
bobSecret, err := bobPrivKey.ECDH(alicePubKey)
panicOnErr(err)
fmt.Printf("Bob's Shared Secret  : %x\n", bobSecret)

// 作成例:
// Alice's Shared Secret: f2d46357b7deab81ea3c068e8c3430ab03f3851c6e6dd7790e26e831bcae73b307d69950e22b5677205aafcdc13c138b
// Bob's Shared Secret  : f2d46357b7deab81ea3c068e8c3430ab03f3851c6e6dd7790e26e831bcae73b307d69950e22b5677205aafcdc13c138b
```

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

---

:::note info
**P521 vs X25519?**

> <font color=darkorange>ECDH の鍵交換には X25519 を選ぶべし</font>

上記「Alice と Bob の事前合意」で、パラーメータの選択肢にある P256 や P384 などの数値は、内部の計算で使われる素数（[メルセンヌ素数](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%AB%E3%82%BB%E3%83%B3%E3%83%8C%E6%95%B0#%E3%83%A1%E3%83%AB%E3%82%BB%E3%83%B3%E3%83%8C%E7%B4%A0%E6%95%B0)）を定めたものです。

- P256 = $2^{256} – 2^{244} + 2^{192} + 2^{96} – 1$
- P384 = $2^{384} – 2^{128} – 2^{96} + 2^{32} – 1$
- P521 = $2^{521} – 1$
- X25519 = $2^{255} - 19$

そして "`crypto/ecdh`" のパッケージは、以下の仕様を元に実装されています。

- P256 〜 P521:
    - [NIST](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A1%E3%83%AA%E3%82%AB%E5%9B%BD%E7%AB%8B%E6%A8%99%E6%BA%96%E6%8A%80%E8%A1%93%E7%A0%94%E7%A9%B6%E6%89%80) が 2009 年に定めた [FIPS 186-3](https://csrc.nist.gov/files/pubs/fips/186-3/final/docs/fips_186-3.pdf), Section D.2.3 〜 D.2.5
- X25519:
    - [IETF](https://ja.wikipedia.org/wiki/Internet_Engineering_Task_Force) が 2016 年に定めた [RFC 7748, Section 5](https://www.rfc-editor.org/rfc/rfc7748.html#section-5)

上記の、仕様の制定者と制定年に注目ください。前者の FIPS 186-3 は 2009 年に制定されたものの、2013 年に後続の FIPS-186 シリーズ（Digital Signature Standard, デジタル署名標準）にバトンを渡しました。

2024/03 現在、[FIPS 186-5](　https://csrc.nist.gov/pubs/fips/186-5/final)（2023/02 制定）が最新なのですが、そこでは [Ed25519](https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%89%E3%83%AF%E3%83%BC%E3%82%BA%E6%9B%B2%E7%B7%9A%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E7%BD%B2%E5%90%8D%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0#Ed25519) がデジタル署名アルゴリズムとして選ばれています。

Golang では Go v1.13 移行から "[crypto/ed25519](https://pkg.go.dev/crypto/ed25519)" パッケージで標準実装されていますが、あくまでも<font color=darkorange>**デジタル署名を行うためのパッケージに特化している**</font>ので、鍵共有の関数は持っていません。

では、同じ `25519` を持つ `Ed25519` を使ったものが ECDH の `X25519` なのかと言うと <font color="darkorange">**`25519` の数値が同じだけで別もの**</font>です。

### `X25519` と `Ed25519` の共通点と違い

- 共通点
    - どちらも [Curve25519](https://ja.wikipedia.org/wiki/Curve25519) と呼ばれる楕円曲線を利用している
- 違い
    - 用途
        - `X25519`: [ECDH](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) の鍵交換プロトコルに準拠させた Curve25519 を使った実装。つまり、DH 鍵交換用
        - `Ed25519`: [EdDSA](https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%89%E3%83%AF%E3%83%BC%E3%82%BA%E6%9B%B2%E7%B7%9A%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E7%BD%B2%E5%90%8D%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0) のデジタル署名アルゴリズムのうち Curve25519 を使った実装。つまり、デジタル署名用
    - 制定者
        - `X25519`: [IETF](https://ja.wikipedia.org/wiki/Internet_Engineering_Task_Force)
        - `Ed25519`: [NIST](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A1%E3%83%AA%E3%82%AB%E5%9B%BD%E7%AB%8B%E6%A8%99%E6%BA%96%E6%8A%80%E8%A1%93%E7%A0%94%E7%A9%B6%E6%89%80)

以上から、特別な事情がない限り、<font color="darkorange">**これから DH 鍵交換を "`crypto/ecdh`" のパッケージで利用する場合は `X25519` 一択**</font>ということになります。

:::