【Golang】Go で OpenPGP のペア鍵を生成する【GopenPGP】

> Go 言語（以下 Golang）で [OpenPGP](https://ja.wikipedia.org/wiki/Pretty_Good_Privacy#OpenPGP) に準拠した公開鍵と秘密鍵を作りたい。

つまり、OpenPGP 互換の鍵ペアを Golang で作ろうと思うも、[Go 公式のドキュメント](https://pkg.go.dev/golang.org/x/crypto@v0.2.0/openpgp)を見ると `Deprecated`（廃止）になっていて、ガチョーン。

しかも、 `golang.org/x/crypto/openpgp` を使っていると、CI/ID などの脆弱性スキャナーで [CVE-2022-27191](https://github.com/advisories/GHSA-8c26-wmh5-6g9v) の脆弱性を指摘される。

さらに、Go の公式は「[後継のパッケージは色々あるけど、私たちの方から『これがオススメ』というものはないよ](https://github.com/golang/go/issues/44226)」ときたもんだ。どうしよう。

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

> 2022/11/12 現在のスター数、更新頻度、利用者数から見て `github.com/ProtonMail/go-crypto` パッケージだが、そこから <strong>`OpenPGP` のみに<ruby>特化<rt>スピンオフ</rt></ruby>させた [GopenPGP](https://gopenpgp.org/) がよさげ</strong>。

1. <strong>[PGP のゴタゴタ](https://ja.wikipedia.org/wiki/Pretty_Good_Privacy#%E6%A6%82%E8%AA%AC)の後、[規格化された](https://www.rfc-editor.org/rfc/rfc2440)のが [OpenPGP](https://ja.wikipedia.org/wiki/Pretty_Good_Privacy#OpenPGP) である。</strong><br><sub><strong>OpenPGP はアプリの実装規格（[RFC4880](https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-rfc4880bis-08)）</strong>なので、[管理団体](https://www.openpgp.org/)は存在しても **OpenPGP というアプリは存在しない**。「OpenPGP なんとか」というのは「OpenPGP（の機能に準拠した）なんとか」という意味。また、OpenPGP は、利用する暗号化アルゴリズムに [RSA](https://ja.wikipedia.org/wiki/RSA%E6%9A%97%E5%8F%B7) や [Curve25519](https://ja.wikipedia.org/wiki/Curve25519) などが選択できる。つまり、**PGP の鍵も、アルゴリズムが RSA なら中身は実質 RSA の鍵と同じ**である。</sub>
1. <strong>有名な [GPG（GnuPG, GNU Privacy Guard）](https://ja.wikipedia.org/wiki/GNU_Privacy_Guard)は、OpenPGP を C 言語で実装したアプリである。</strong><br><sub>つまり、GPG も OpenPGP の 1 つ（の実装）である。他言語でも[実装ライブラリ](https://www.openpgp.org/software/developer/)や、[実装アプリ](https://www.openpgp.org/software/)は多数存在する。また、Golang の [OpenPGP 用パッケージも複数ある](https://pkg.go.dev/search?limit=25&m=package&q=OpenPGP)。</sub>
1. <strong>[GopenPGP](https://gopenpgp.org/) を検討してどうか。</strong><br><sub>[Mozilla](https://ja.wikipedia.org/wiki/Mozilla) の暗号用エディタ [SOPS](https://github.com/mozilla/sops)（Go 製）で現在使っているのが [Proton Mail](https://ja.wikipedia.org/wiki/Proton_Mail) の [github.com/ProtonMail/go-crypto](https://github.com/mozilla/sops/blob/e1edc059487ddd14236dfe47267b05052f6c20b4/pgp/keysource.go#L20-L21) パッケージ。本家 `golang.org/x/crypto/openpgp` をフォークして現在でもメンテナンスしている。そして、そこから OpenPGP のみをスピンオフさせて使いやすくしたのが [GopenPGP](https://gopenpgp.org/) の [github.com/ProtonMail/gopenpgp](https://github.com/ProtonMail/gopenpgp) パッケージ。良さげだったら GitHub スターをつけよう。</sub>

:::note info

この記事は、OpenPGP に準拠したアプリで RSA アルゴリズムを利用したい場合を想定しています。

- 単純に<font color="goldenrod">**署名に使いたいだけ**</font>なら [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) を使った方がいい。（Git のコミットにも使える）
    - Go 公式パッケージ: [golang.org/x/crypto/ed25519](https://pkg.go.dev/crypto/ed25519)
- 単純に<font color="goldenrod">**データを暗号化したいだけ**</font>なら共有鍵暗号の [AES-256](https://ja.wikipedia.org/wiki/Advanced_Encryption_Standard) を使った方がいい。
    - Go 公式パッケージ: [golang.org/x/crypto/aes](https://pkg.go.dev/crypto/aes)
- 単純に<font color="goldenrod">**公開鍵・秘密鍵暗号を利用したいだけ**</font>なら、アルゴリズムは RSA より ECDH など [Curve25519](https://ja.wikipedia.org/wiki/Curve25519)（`x25519` 関数の実装）を使って、AES-256 の共通暗号鍵を交換した方がいい。
    - Go 公式パッケージ: [golang.org/x/crypto/curve25519](https://pkg.go.dev/golang.org/x/crypto/curve25519)
    - GopenPGP パッケージ: [github.com/ProtonMail/gopenpgp/v2](https://github.com/ProtonMail/gopenpgp#generate-key)
        - （内部的には Go 公式のライブラリのラッパーだが、OpenPGP に準拠しつつ、Curve25519 アルゴリズムを利用する）
- 単純に<font color="goldenrod">**最先端のハイブリッド暗号[^hybrid]を試したいだけ**</font>なら、[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) による[耐量子演算暗号アルゴリズムのコンテスト勝者](https://csrc.nist.gov/Projects/post-quantum-cryptography/selected-algorithms-2022)の [CRYSTALS チーム](https://pq-crystals.org/)による「[KYBER アルゴリズム](https://pq-crystals.org/kyber/index.shtml)」を試してみるといい。
    - NIST の正式制定待ちなものの、[OpenPGP のアルゴリズムとしても取り込まれる予定](https://www.ietf.org/archive/id/draft-wussler-openpgp-pqc-01.html#name-supported-public-key-algori)。`Kyber768` と + 楕円曲線暗号 [X25519](https://ja.wikipedia.org/wiki/Curve25519) の組み合わせは `MUST`（必須）となることが決まっている。
    - 公式の C 言語による実装: [Kyber](https://github.com/pq-crystals/kyber) | pq-crystals @ GitHub
    - Cloudflare による Go 言語実装: [CIRCL](https://github.com/cloudflare/circl) 暗号ライブラリ @ GitHub
        - [Kyber PKE](https://github.com/cloudflare/circl#post-quantum-public-key-encryption)（公開鍵暗号）
            - 使い方: [【Golang】Kyber アルゴリズムで公開鍵暗号（PKE）](https://qiita.com/KEINOS/items/1e31b09d72c8ce349499) @ Qiita
        - [Kyber KEM](https://github.com/cloudflare/circl#post-quantum-key-encapsulation-methods)（共通鍵交換暗号）
        - おまけ
            - [Dilithium Digital Signature](https://github.com/cloudflare/circl#post-quantum-digital-signature-schemes)（電子署名。[Dilithium](https://pq-crystals.org/dilithium/index.shtml) は、[量子コンピューター耐性のある電子署名アルゴリズム・コンペの勝者の 1 つ](https://csrc.nist.gov/Projects/post-quantum-cryptography/selected-algorithms-2022)で、同上の CRYSTALS チームによるアルゴリズム）
:::

[^hybrid]: https://ja.wikipedia.org/wiki/%E3%83%8F%E3%82%A4%E3%83%96%E3%83%AA%E3%83%83%E3%83%89%E6%9A%97%E5%8F%B7

- 併せて読みたい
    - [GopenPGP](https://text.baldanders.info/openpgp/gopenpgp/) @ text.Baldanders.info
      - GopenPGP に至った、経緯に詳しい
    - [GopenPGP について軽く調べておく](https://zenn.dev/spiegel/scraps/bec4b33c8c803f) @ Zenn.dev

## TS; DR

```go
go get "github.com/ProtonMail/gopenpgp/v2"
```

```go:GolangでPGP鍵のペアを作成
package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/ProtonMail/gopenpgp/v2/crypto"
	"github.com/ProtonMail/gopenpgp/v2/helper"
)

func main() {
	passphrase := []byte("MyVeryStrongSecretPhrase")
	name := "My Name"
	email := "my.name@example.com"

	// Create RSA Secret Key (PEM 形式)
	rsaBits := 4096

    // 鍵のバリエーション:
    //
    // RSA, string
    //   rsaKey, err := helper.GenerateKey(name, email, passphrase, "rsa", rsaBits)
    //
    // Curve25519, string
    //   ecKey, err := helper.GenerateKey(name, email, passphrase, "x25519", 0)
    //
    // RSA, Key struct
    //   rsaKey, err := crypto.GenerateKey(name, email, "rsa", rsaBits)
    //
    // Curve25519, Key struct
    //   ecKey, err := crypto.GenerateKey(name, email, "x25519", 0)
    //
    // 以下は RSA-4096 の鍵ペア作成
	rsaKey, err := helper.GenerateKey(name, email, passphrase, "rsa", rsaBits)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("RSA4096 Secret:\n%v\n\n", rsaKey)

	// Generate RSA Public Key (PEM 形式)
	keyRing, err := crypto.NewKeyFromArmoredReader(strings.NewReader(rsaKey))
	if err != nil {
		log.Fatal(err)
	}

	publicKey, err := keyRing.GetArmoredPublicKey()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("RSA4096 Public:\n%v\n", publicKey)

	// Output:
	// RSA4096 Secret:
	// -----BEGIN PGP PRIVATE KEY BLOCK-----
	// Version: GopenPGP 2.4.10
	// Comment: https://gopenpgp.org
	//
	// xcaGBGNuTusBEAC16zBOWYqd6uEK7aUIHz3VKGXc9X/94yXaIq4xdVJd/MZp/379
	// 8lfWxkwuN/+KT2TXMQerq7s765ZI1t25p+YPtzXg+fr4JdVlx6EvhGatuCstBTys
	// ** snip **
	// Bd4MBg+SCbI9+X04nDtIOnFQjv7Vw3l9PGe1HXhCOZlIZXZ3x2kKCjLBZn+/qi9b
	// lMHXXA3VMuZ5IeHdL5EZJXPbhFhU/7Ijw1h2pqoUq/zQoRMLXQ==
	// =bgBm
	// -----END PGP PRIVATE KEY BLOCK-----
	//
	// RSA4096 Public:
	// -----BEGIN PGP PUBLIC KEY BLOCK-----
	// Version: GopenPGP 2.4.10
	// Comment: https://gopenpgp.org
	//
	// xsFNBGNuTusBEAC16zBOWYqd6uEK7aUIHz3VKGXc9X/94yXaIq4xdVJd/MZp/379
	// 8lfWxkwuN/+KT2TXMQerq7s765ZI1t25p+YPtzXg+fr4JdVlx6EvhGatuCstBTys
	// ** snip **
	// dKRdba+jzP/OIn8YJsEF3gwGD5IJsj35fTicO0g6cVCO/tXDeX08Z7UdeEI5mUhl
	// dnfHaQoKMsFmf7+qL1uUwddcDdUy5nkh4d0vkRklc9uEWFT/siPDWHamqhSr/NCh
	// Ewtd
	// =mwx1
	// -----END PGP PUBLIC KEY BLOCK-----
}

```