Go で RFC9580 準拠の OpenPGP 鍵ペアを作成する
Go 言語(以下 golang)で、GopenPGP v3 モジュールを使って RFC9580 に準拠した OpenPGP(パケット v6)鍵を生成したい。
この記事は、以下の GopenPGP v2 で OpenPGP のペア鍵を生成する 記事のフォローアップ記事です。どうやら「最新の RFC9580 にすれば良い」というものではなさそうです(文末の TS; DR 参照)。
TL; DR 何はともあれ動くソースコード
まずは RFC9580 準拠の OpenPGP 鍵を生成するコードです。
セキュリティ・レベルを "High" に設定しているので、内部の楕円曲線方式が Ed25519
(Standard
)から Ed448
(High
)に強化されています。
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/ProtonMail/gopenpgp/v3/constants"
"github.com/ProtonMail/gopenpgp/v3/crypto"
"github.com/ProtonMail/gopenpgp/v3/profile"
)
func main() {
yourName := "myName"
yourEmail := "my@example.com"
// Create a PGP handler with the RFC9580 profile (OpenPGP v6 standard)
// * Ref: https://www.ietf.org/rfc/rfc9580.pdf
pgpHandler := crypto.PGPWithProfile(profile.RFC9580())
// Create a new PGP key generator with the desired user ID.
pgpGenerator := pgpHandler.KeyGeneration().AddUserId(yourName, yourEmail).New()
// Generate the key with high security level. (choices: constants.StandardSecurity)
ecKey, err := pgpGenerator.GenerateKeyWithSecurity(constants.HighSecurity)
if err != nil {
panic(err)
}
// Get the ASCII armored private key
fmt.Println("Private Key (ASCII Armored):")
privKeyAsc, err := ecKey.Armor()
if err != nil {
panic(err)
}
fmt.Println(privKeyAsc)
// Get the ASCII armored public key
fmt.Println("Public Key (ASCII Armored):")
pubKeyAsc, err := ecKey.GetArmoredPublicKey()
if err != nil {
panic(err)
}
fmt.Println(pubKeyAsc)
// Save the public key to a file
pathFileKey := filepath.Join("..", "testdata", "pubKey.asc")
if err := os.WriteFile(pathFileKey, []byte(pubKeyAsc), 0644); err != nil {
panic(err)
}
}
生成された鍵の検証
上記で生成された OpenPGP 鍵の公開鍵が「他のアプリから読み込めるか」検証してみたいと思いました。
ところが、GnuPG(v2.4.8 現在)では、RFC9580(OpenPGP の v6)には公式対応していませんでした(その理由は後述)。
$ gpg --list-packets ./test_pubKey.asc
gpg: packet(6) with unknown version 6
gpg: packet(2) with unknown version 6
gpg: packet(2) with unknown version 6
gpg: packet(14) with unknown version 6
gpg: packet(2) with unknown version 6
gpg: 有効なOpenPGPデータが見つかりません。
# off=0 ctb=c6 tag=6 hlen=2 plen=67 new-ctb
:key packet: [unknown version]
# off=69 ctb=c2 tag=2 hlen=3 plen=226 new-ctb
:signature packet: [unknown version]
# off=298 ctb=cd tag=13 hlen=2 plen=23 new-ctb
:user ID packet: "myName <my@example.com>"
# off=323 ctb=c2 tag=2 hlen=3 plen=205 new-ctb
:signature packet: [unknown version]
# off=531 ctb=ce tag=14 hlen=2 plen=66 new-ctb
:key packet: [unknown version]
# off=599 ctb=c2 tag=2 hlen=3 plen=205 new-ctb
:signature packet: [unknown version]
$ gpg --version
gpg (GnuPG) 2.4.8
libgcrypt 1.11.1
Copyright (C) 2025 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Home: /Users/KEINOS/.gnupg
サポートしているアルゴリズム:
公開鍵: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
暗号方式: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256,
TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256
ハッシュ: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
圧縮: 無圧縮, ZIP, ZLIB, BZIP2
OpenPGP v6 (RFC9580) に準拠したアプリでは、Rust で書かれた Sequoia PGP が対応していたので試してみました。
Sequoia PGP(sq
)では、ちゃんと公開鍵を認識し、鍵情報も取得できました。
$ sq version
sq 1.3.1
using sequoia-openpgp 2.0.0
with cryptographic backend Nettle 3.10 (Cv448: true, OCB: true)
$ # 鍵内の証明書に関する統計情報を収集(構文チェック)
$ sq cert lint --cert-file ./test_pubKey.asc
Examined 1 certificate.
0 certificates are invalid and were not linted. (GOOD)
1 certificate was linted.
0 of the 1 certificates (0%) have at least one issue. (GOOD)
0 of the linted certificates were revoked.
0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one
non-revoked User ID:
0 have at least one User ID protected by SHA-1. (GOOD)
0 have all User IDs protected by SHA-1. (GOOD)
1 of the non-revoked linted certificates has at least one
non-revoked, live subkey:
0 have at least one non-revoked, live subkey with a binding signature that uses SHA-1. (GOOD)
0 of the non-revoked linted certificates have at least one
non-revoked, live, signing-capable subkey:
0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)
$ # 鍵情報をダンプ出力
$ sq packet dump ./test_pubKey.asc
Public-Key Packet, new CTB, 67 bytes
Version: 6
Creation time: 2025-06-25 11:39:51 UTC
Pk algo: Ed448
Pk size: 456 bits
Fingerprint: D2C036E970AD4CEE20F87B6E00A3F60B9711901C87BC7507058481455FFA8267
KeyID: D2C036E970AD4CEE
Signature Packet, new CTB, 226 bytes
Version: 6
Type: DirectKey
Pk algo: Ed448
Hash algo: SHA512
Hashed area:
Signature creation time: 2025-06-25 11:39:51 UTC (critical)
Symmetric algo preferences: AES256, AES128
Hash preferences: SHA512, SHA256
Compression preferences: Uncompressed, Zlib
Key flags: CS (critical)
Features: SEIPDv1, SEIPDv2
Issuer Fingerprint: D2C036E970AD4CEE20F87B6E00A3F60B9711901C87BC7507058481455FFA8267 (critical)
AEAD preferences: AES256+OCB, AES128+OCB
Digest prefix: 35DD
Salt: 83279F18B35CCBA007821B9A6B5F6769264C74BB7BB345EC2B9B8F32FEE3BB99
Level: 0 (signature over data)
User ID Packet, new CTB, 23 bytes
Value: myName <my@example.com>
Signature Packet, new CTB, 205 bytes
Version: 6
Type: PositiveCertification
Pk algo: Ed448
Hash algo: SHA512
Hashed area:
Signature creation time: 2025-06-25 11:39:51 UTC (critical)
Primary User ID: true
Issuer Fingerprint: D2C036E970AD4CEE20F87B6E00A3F60B9711901C87BC7507058481455FFA8267 (critical)
Digest prefix: C759
Salt: 7AC7EF086F75553A7EBCDA452E7644EDB167B058AC7F0C4A84156CF7CA130BDD
Level: 0 (signature over data)
Public-Subkey Packet, new CTB, 66 bytes
Version: 6
Creation time: 2025-06-25 11:39:51 UTC
Pk algo: X448
Pk size: 448 bits
Fingerprint: 90588080DC08E243F7B69483A2757BA48DC6362F8A519702A4578DC58956FF12
KeyID: 90588080DC08E243
Signature Packet, new CTB, 205 bytes
Version: 6
Type: SubkeyBinding
Pk algo: Ed448
Hash algo: SHA512
Hashed area:
Signature creation time: 2025-06-25 11:39:51 UTC (critical)
Key flags: EtEr (critical)
Issuer Fingerprint: D2C036E970AD4CEE20F87B6E00A3F60B9711901C87BC7507058481455FFA8267 (critical)
Digest prefix: 75F0
Salt: 9606C0A4C711CF349BB9D3FC1152BBFDD9BFB4FF220323D8C866FD0E085AACDA
Level: 0 (signature over data)
TS; DR
前回の記事と大きく違うのが、サポートする OpenPGP の仕様のバージョンです。
ながらくメインストリームにいた RFC4880(2007年公開)から、2025/06/26 現在、OpenPGP の最新の仕様は RFC9580(2024年7月公開)となりました。
RFC9580 の変更点はたくさんあるのですが、やはり RSA 鍵を明確に廃止(deprecated
)と言い切ったことでしょう(RFC9580, 9.1 "Public Key Algorithms" 参照)。
しかし、調べてみて「これは大変だ」と思ったのが、天下の GnuPG は RFC9580
に反対しており、そもそも「RFC9580
には準拠しない」という方針に舵を切っていたことです。
というのも、RFC4880
(v4)の改善提案として進められていた RFC4880bis
(v5)に対し、この提案に大いに貢献していた GnuPG 開発者の意見を反映せずに、大幅な設計変更を加えた RFC9580
が制定されました。その結果、既存実装との互換性が薄れることを懸念した GnuPG は、RFC4880bis
を基にした独自仕様「LibrePGP」を、自らの実装に反映しています。
RFC9580 の著者を見ると、GnuPG のメンバーが見当たらず、RFC9580 の実装に積極的な ProtonMail
や Sequoia PGP
のメンバーなのも、何か色々あったことが伺えます。
つまり「OpenPGP の実装」と「LibrePGP の実装」の、ダブルスタンダード(二大仕様)が生まれたことになります。
OpenPGP の実装と言えば GnuPG だったものが、そうではなくなったということです。しかも GnuPG に依存しているアプリは多いため、いやでも LibrePGP の仕様も網羅しないといけなくなったのが辛いところです。