Go 言語(以下 Golang)で OpenPGP に準拠した公開鍵と秘密鍵を作りたい。
つまり、OpenPGP 互換の鍵ペアを Golang で作ろうと思うも、Go 公式のドキュメントを見ると Deprecated
(廃止)になっていて、ガチョーン。
しかも、 golang.org/x/crypto/openpgp
を使っていると、CI/ID などの脆弱性スキャナーで CVE-2022-27191 の脆弱性を指摘される。
さらに、Go の公式は「後継のパッケージは色々あるけど、私たちの方から『これがオススメ』というものはないよ」ときたもんだ。どうしよう。
この記事は GopenPGP v2 を利用した記事です。
- GopenPGP v3 による鍵生成の記事も書きました
- PKE 用の暗号鍵でなく、量子耐性のある Dilithium 署名鍵(FIPS204)の記事も書きました
TL; DR (今北産業)
2022/11/12 現在のスター数、更新頻度、利用者数から見て
github.com/ProtonMail/go-crypto
パッケージだが、そこからOpenPGP
のみに特化させた GopenPGP がよさげ。
-
PGP のゴタゴタの後、規格化されたのが OpenPGP である。
OpenPGP はアプリの実装規格(RFC4880)なので、管理団体は存在しても OpenPGP というアプリ自体は実は存在しない。「OpenPGP なんとか」というのは「OpenPGP(の仕様に準拠した)なんとか」という意味。また、OpenPGP は、利用する暗号化アルゴリズムに RSA や Curve25519 などが選択できる。つまり、PGP の鍵も、アルゴリズムが RSA なら中身は実質 RSA の鍵と同じである。 -
有名な GPG(GnuPG, GNU Privacy Guard)は、OpenPGP を C 言語で実装したアプリである。
つまり、GPG も OpenPGP の 1 つ(の実装)である。他言語でも実装ライブラリや、実装アプリは多数存在する。また、Golang の OpenPGP 用パッケージも複数ある。 -
GopenPGP を検討してどうか。
Mozilla の暗号用エディタ SOPS(Go 製)で現在使っているのが Proton Mail の github.com/ProtonMail/go-crypto パッケージ。本家golang.org/x/crypto/openpgp
をフォークして現在でもメンテナンスしている。そして、そこから OpenPGP のみをスピンオフさせて使いやすくしたのが GopenPGP の github.com/ProtonMail/gopenpgp パッケージ。良さげだったら GitHub スターをつけよう。
この記事は、俺様 Go アプリで OpenPGP に準拠した鍵を生成・利用したい場合を想定しています。次項のサンプルコードでは RSA を利用していますが、OpenPGP では RSA 鍵の生成は非推奨となりました。
- 単純に署名に使いたいだけなら Ed25519 を使った方がいい。(Git のコミットにも使える)
- Go 公式パッケージ: golang.org/x/crypto/ed25519
- 単純にデータを暗号化したいだけなら共有鍵暗号の AES-256 を使った方がいい。
- Go 公式パッケージ: golang.org/x/crypto/aes
- 単純に公開鍵・秘密鍵暗号を利用したいだけなら、アルゴリズムは RSA より ECDH など Curve25519(
x25519
関数の実装)を使って、AES-256 の共通暗号鍵を交換した方がいい。- Go 公式パッケージ: golang.org/x/crypto/curve25519
- GopenPGP パッケージ: github.com/ProtonMail/gopenpgp/v2
- (内部的には Go 公式のライブラリのラッパーだが、OpenPGP に準拠しつつ、Curve25519 アルゴリズムを利用する)
- 単純に最先端のハイブリッド暗号1を試したいだけなら、NIST による耐量子演算暗号アルゴリズムのコンテスト勝者の CRYSTALS チームによる「KYBER アルゴリズム」を試してみるといい。
- NIST の正式制定待ちなものの、OpenPGP のアルゴリズムとしても取り込まれる予定。
Kyber768
と + 楕円曲線暗号 X25519 の組み合わせはMUST
(必須)となることが決まっている。 - 公式の C 言語による実装: Kyber | pq-crystals @ GitHub
- Cloudflare による Go 言語実装: CIRCL 暗号ライブラリ @ GitHub
-
Kyber PKE(公開鍵暗号)
- 使い方: 【Golang】Kyber アルゴリズムで公開鍵暗号(PKE) @ Qiita
- Kyber KEM(共通鍵交換暗号)
- おまけ
- Dilithium Digital Signature(電子署名。Dilithium は、量子コンピューター耐性のある電子署名アルゴリズム・コンペの勝者の 1 つで、同上の CRYSTALS チームによるアルゴリズム)
-
Kyber PKE(公開鍵暗号)
- NIST の正式制定待ちなものの、OpenPGP のアルゴリズムとしても取り込まれる予定。
- 併せて読みたい
-
GopenPGP @ text.Baldanders.info
- GopenPGP に至った、経緯に詳しい
- GopenPGP について軽く調べておく @ Zenn.dev
-
GopenPGP @ text.Baldanders.info
TS; DR
OpenPGP の RFC9580 で、"9.1. Public Key Algorithms" のセクションによると、RSA 鍵は非推奨となっています。特に、鍵を新規生成する場合、RSA 鍵の生成は "MUST NOT" と強い非推奨となっています。
RSA keys are deprecated and SHOUD NOT be generated but may be interpreted. RSA EncryptOnly and RSA Sign-Only are deprecated and MUST NOT be generated (see Section 12.4).
Elgamal keys are deprecated and MUST NOT be generated (see Section 12.6). DSA keys are deprecated and MUST NOT be generated (see Section 12.5). For notes on Elgamal Encrypt or Sign and X9.42, see Section 12.8. Implementations MAY implement any other algorithm.
以下のサンプル・コードでは RSA 鍵を生成していますが、従来の鍵生成要素の感覚と比較するためなので、実際に生成する場合は、Curve25519
をおすすめします。
go get "github.com/ProtonMail/gopenpgp/v2"
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-----
}