Files
encryptx/gpgx/gpgx.go
T
2026-09-30 15:49:12 +08:00

656 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package gpgx 提供基于 OpenPGP(RFC 4880 / RFC 9580)的密钥环读取、
// 加解密、签名验签与密钥生成能力。
//
// 实现基于 github.com/ProtonMail/go-crypto/openpgp —— 官方推荐的
// golang.org/x/crypto/openpgp 替代品(后者已被 govulncheck 判定为
// GO-2026-5932:unmaintained, unsafe by design, and has known security
// issues,且没有修复版本)。
//
// 设计约定:
// - 所有导出函数都不 panic,全部通过 error 返回失败(用 %w 包装以便
// errors.Is / errors.As 判断)。
// - 所有 io.WriteCloser 都会在写出后 Close,并检查 Close 返回的错误
// (加密流的结尾数据在 Close 时才真正写出,漏检会导致密文不完整)。
// - 需要私钥的操作(解密、签名)在传入 passphrase 时操作密钥环的副本,
// 不会修改调用方传入的 openpgp.EntityList。
// - 本包不向 stdout/stderr 输出任何内容。
package gpgx
import (
"bytes"
"crypto"
"errors"
"fmt"
"io"
"os"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
// 密钥长度约束。OpenPGP 允许更短的 RSA 密钥,但本包拒绝生成弱密钥。
const (
// MinRSABits 是 GenerateKeyPair 接受的 RSA 最小位数。
MinRSABits = 2048
// DefaultRSABits 是 GenerateKeyPair 的 RSA 默认位数。
DefaultRSABits = 2048
)
// armorHeader 用于快速判断输入是否为 ASCII armor 文本。
const armorHeader = "-----BEGIN PGP"
// ---------------------------------------------------------------- 密钥环读取
// ReadKeyRing 从 r 读取 OpenPGP 密钥环,自动识别 armored 与二进制格式。
//
// 返回的 EntityList 同时包含公钥与(可能已加密的)私钥材料。
func ReadKeyRing(r io.Reader) (openpgp.EntityList, error) {
raw, err := readAll(r)
if err != nil {
return nil, fmt.Errorf("gpgx: 读取密钥环数据失败: %w", err)
}
if len(raw) == 0 {
return nil, errors.New("gpgx: 密钥环数据为空")
}
return parseKeyRing(raw)
}
// ReadArmoredKeyRing 从 r 读取 ASCII armor 编码的 OpenPGP 密钥环。
//
// 若输入不是合法的 armor 文本(例如二进制密钥环或垃圾数据),返回 error。
func ReadArmoredKeyRing(r io.Reader) (openpgp.EntityList, error) {
raw, err := readAll(r)
if err != nil {
return nil, fmt.Errorf("gpgx: 读取 armored 密钥环数据失败: %w", err)
}
if len(raw) == 0 {
return nil, errors.New("gpgx: armored 密钥环数据为空")
}
if !isArmored(raw) {
return nil, errors.New("gpgx: 输入不是 ASCII armor 文本(缺少 \"-----BEGIN PGP\" 头)")
}
entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(raw))
if err != nil {
return nil, fmt.Errorf("gpgx: 解析 armored 密钥环失败: %w", err)
}
if len(entities) == 0 {
return nil, errors.New("gpgx: armored 密钥环中不包含任何实体")
}
return entities, nil
}
// ReadKeyRingFile 从文件读取 OpenPGP 密钥环,自动识别 armored 与二进制格式。
//
// 内部使用 os.ReadFile,不会泄漏文件句柄。
func ReadKeyRingFile(path string) (openpgp.EntityList, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("gpgx: 读取密钥环文件 %q 失败: %w", path, err)
}
if len(raw) == 0 {
return nil, fmt.Errorf("gpgx: 密钥环文件 %q 为空", path)
}
entities, err := parseKeyRing(raw)
if err != nil {
return nil, fmt.Errorf("gpgx: 解析密钥环文件 %q 失败: %w", path, err)
}
return entities, nil
}
// parseKeyRing 按内容自动选择 armored 或二进制解析。
func parseKeyRing(raw []byte) (openpgp.EntityList, error) {
var (
entities openpgp.EntityList
err error
)
if isArmored(raw) {
entities, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(raw))
} else {
entities, err = openpgp.ReadKeyRing(bytes.NewReader(raw))
}
if err != nil {
return nil, fmt.Errorf("解析密钥环失败: %w", err)
}
if len(entities) == 0 {
return nil, errors.New("密钥环中不包含任何实体")
}
return entities, nil
}
// isArmored 判断数据是否为 PGP ASCII armor(允许前置空白)。
func isArmored(data []byte) bool {
return bytes.HasPrefix(bytes.TrimLeft(data, " \t\r\n"), []byte(armorHeader))
}
// maxUnwrappedArmorSize 限制单个 armor 块解出的原始报文大小,避免畸形输入
// 造成无界内存占用。
const maxUnwrappedArmorSize = 64 << 20 // 64 MiB
// unwrapArmor 在输入为 ASCII armor 时返回其内部的原始报文字节;否则原样返回。
//
// 该函数不校验 armor 块的 Type 字段:调用方(ReadMessage /
// CheckDetachedSignature)会对解出的报文本身做严格校验,而 armor 的 CRC
// 校验由 crypto/openpgp 的 packet 层与完整性保护(MDC/AEAD)负责。
func unwrapArmor(data []byte) ([]byte, error) {
if !isArmored(data) {
return data, nil
}
block, err := armor.Decode(bytes.NewReader(data))
if err != nil {
return nil, err
}
if block == nil {
return nil, errors.New("armor 块为空")
}
raw, err := io.ReadAll(io.LimitReader(block.Body, maxUnwrappedArmorSize+1))
if err != nil {
return nil, err
}
if len(raw) > maxUnwrappedArmorSize {
return nil, fmt.Errorf("armor 块解出的报文超过 %d 字节上限", maxUnwrappedArmorSize)
}
if len(raw) == 0 {
return nil, errors.New("armor 块不包含任何数据")
}
return raw, nil
}
// ------------------------------------------------------------------ 加密解密
// Encrypt 使用 recipients 中的公钥加密 plaintext,返回二进制 OpenPGP 报文。
func Encrypt(recipients openpgp.EntityList, plaintext []byte) ([]byte, error) {
if err := validateRecipients(recipients); err != nil {
return nil, err
}
var buf bytes.Buffer
plaintextWriter, err := openpgp.Encrypt(&buf, recipients, nil, nil, nil)
if err != nil {
return nil, fmt.Errorf("gpgx: 初始化加密流失败: %w", err)
}
if err := writeAndClose(plaintextWriter, plaintext); err != nil {
return nil, fmt.Errorf("gpgx: 加密失败: %w", err)
}
return buf.Bytes(), nil
}
// EncryptArmored 使用 recipients 中的公钥加密 plaintext,返回 ASCII armor 报文。
func EncryptArmored(recipients openpgp.EntityList, plaintext []byte) ([]byte, error) {
binaryMessage, err := Encrypt(recipients, plaintext)
if err != nil {
return nil, err
}
var buf bytes.Buffer
armorWriter, err := armor.Encode(&buf, openpgp.MessageType, nil)
if err != nil {
return nil, fmt.Errorf("gpgx: 初始化 armor 编码器失败: %w", err)
}
if _, err := armorWriter.Write(binaryMessage); err != nil {
_ = armorWriter.Close()
return nil, fmt.Errorf("gpgx: 写出 armor 数据失败: %w", err)
}
if err := armorWriter.Close(); err != nil {
return nil, fmt.Errorf("gpgx: 关闭 armor 编码器失败: %w", err)
}
return buf.Bytes(), nil
}
// EncryptWithPassphrase 使用口令对称加密 plaintext(等价于 gpg -c),
// 返回二进制 OpenPGP 报文;解密端使用 DecryptWithPassphrase。
//
// 对称加密同样受完整性保护(MDC/AEAD),不需要密钥环。
func EncryptWithPassphrase(passphrase, plaintext []byte) ([]byte, error) {
if len(passphrase) == 0 {
return nil, errors.New("gpgx: passphrase 为空,无法进行对称加密")
}
var buf bytes.Buffer
plaintextWriter, err := openpgp.SymmetricallyEncrypt(&buf, passphrase, nil, nil)
if err != nil {
return nil, fmt.Errorf("gpgx: 初始化对称加密流失败: %w", err)
}
if err := writeAndClose(plaintextWriter, plaintext); err != nil {
return nil, fmt.Errorf("gpgx: 对称加密失败: %w", err)
}
return buf.Bytes(), nil
}
// Decrypt 使用 keyring 解密 ciphertext,自动识别 armored 与二进制格式。
//
// 若会话密钥被公钥加密,keyring 中必须包含对应私钥;私钥本身可以是
// 未加密的。已加密的私钥请使用 DecryptWithPassphrase。
func Decrypt(keyring openpgp.EntityList, ciphertext []byte) ([]byte, error) {
return decryptWith(keyring, ciphertext, nil, nil)
}
// DecryptWithPassphrase 使用 keyring 与 passphrase 解密 ciphertext。
//
// passphrase 用于解开已加密的私钥;也可以直接解密「口令加密」(对称加密,
// gpg -c)的报文。若私钥未加密,传入非空 passphrase 同样可以正常工作。
func DecryptWithPassphrase(keyring openpgp.EntityList, ciphertext, passphrase []byte) ([]byte, error) {
if len(keyring) == 0 {
return nil, errors.New("gpgx: 密钥环为空,无法解密")
}
// 在副本上解密私钥,避免修改调用方传入的密钥环。
copied := cloneKeyRing(keyring)
if err := decryptedPrivateKeys(copied, passphrase); err != nil {
return nil, err
}
var prompt openpgp.PromptFunction
if len(passphrase) > 0 {
// copied 中的私钥已在上一步解开;此回调用于覆盖「口令加密」报文
// 以及上一步未能解开的私钥,同时避免库无限重试同一口令。
prompt = oneShotPassphrasePrompt(passphrase)
}
return decryptWith(copied, ciphertext, prompt, nil)
}
// decryptWith 是解密路径的公共实现。keyring 由调用方保证不会被共享。
func decryptWith(keyring openpgp.EntityList, ciphertext []byte, prompt openpgp.PromptFunction, config *packet.Config) ([]byte, error) {
if len(ciphertext) == 0 {
return nil, errors.New("gpgx: 密文为空")
}
if len(keyring) == 0 {
return nil, errors.New("gpgx: 密钥环为空,无法解密")
}
// openpgp.ReadMessage 只接受二进制报文(packet.Reader 不会解析 armor),
// 因此 armored 输入需要先取出 armor 内部的原始字节。
binaryMessage, err := unwrapArmor(ciphertext)
if err != nil {
return nil, fmt.Errorf("gpgx: 解析加密报文失败: %w", err)
}
md, err := openpgp.ReadMessage(bytes.NewReader(binaryMessage), keyring, prompt, config)
if err != nil {
return nil, fmt.Errorf("gpgx: 解析加密报文失败: %w", err)
}
if md.UnverifiedBody == nil {
return nil, errors.New("gpgx: 加密报文不包含可读数据(可能不是加密报文)")
}
// 必须读到 EOF:完整性保护(MDC/AEAD)在 EOF 时才校验,提前返回会
// 跳过校验。
plaintext, err := io.ReadAll(md.UnverifiedBody)
if err != nil {
return nil, fmt.Errorf("gpgx: 读取或校验明文失败: %w", err)
}
if md.IsSigned {
if md.SignatureError != nil {
return nil, fmt.Errorf("gpgx: 报文的签名校验失败: %w", md.SignatureError)
}
if md.Signature == nil {
return nil, errors.New("gpgx: 报文声称已签名,但未找到可验证的签名")
}
}
return plaintext, nil
}
// ---------------------------------------------------------------- 签名与验签
// Sign 使用 keyring 中的私钥对 plaintext 生成内联签名报文
// (明文内嵌于 PGP 报文内,可直接被 Verify 还原)。
//
// 若私钥已加密,必须提供正确的 passphrase;私钥未加密时可传 nil。
func Sign(keyring openpgp.EntityList, plaintext, passphrase []byte) ([]byte, error) {
if len(keyring) == 0 {
return nil, errors.New("gpgx: 密钥环为空,无法签名")
}
copied := cloneKeyRing(keyring)
if err := decryptedPrivateKeys(copied, passphrase); err != nil {
return nil, err
}
var buf bytes.Buffer
plaintextWriter, err := openpgp.Sign(&buf, copied[0], nil, nil)
if err != nil {
return nil, fmt.Errorf("gpgx: 初始化签名流失败: %w", err)
}
if err := writeAndClose(plaintextWriter, plaintext); err != nil {
return nil, fmt.Errorf("gpgx: 签名失败: %w", err)
}
return buf.Bytes(), nil
}
// Verify 验证内联签名报文并返回其中的明文。
//
// 返回 error 的情况包括:签名不合法、签名者不在 keyring 中、报文被篡改,
// 或报文并非合法的 OpenPGP 签名报文。
func Verify(keyring openpgp.EntityList, signedMessage []byte) ([]byte, error) {
if len(signedMessage) == 0 {
return nil, errors.New("gpgx: 签名报文为空")
}
if len(keyring) == 0 {
return nil, errors.New("gpgx: 密钥环为空,无法验签")
}
// openpgp.ReadMessage 只接受二进制报文,armored 输入需要先解出原始字节。
binaryMessage, err := unwrapArmor(signedMessage)
if err != nil {
return nil, fmt.Errorf("gpgx: 解析签名报文失败: %w", err)
}
md, err := openpgp.ReadMessage(bytes.NewReader(binaryMessage), cloneKeyRing(keyring), nil, nil)
if err != nil {
return nil, fmt.Errorf("gpgx: 解析签名报文失败: %w", err)
}
if md.UnverifiedBody == nil {
return nil, errors.New("gpgx: 签名报文不包含可读数据(可能不是签名报文)")
}
// 签名在读取到 EOF 时才完成校验。
plaintext, err := io.ReadAll(md.UnverifiedBody)
if err != nil {
return nil, fmt.Errorf("gpgx: 读取签名报文明文失败: %w", err)
}
if !md.IsSigned {
return nil, errors.New("gpgx: 报文未包含签名")
}
if md.SignatureError != nil {
return nil, fmt.Errorf("gpgx: 签名校验失败: %w", md.SignatureError)
}
if md.Signature == nil {
return nil, errors.New("gpgx: 未找到可验证的签名")
}
return plaintext, nil
}
// SignDetachedArmored 为 plaintext 生成 ASCII armor 编码的分离签名。
//
// 若私钥已加密,必须提供正确的 passphrase;私钥未加密时可传 nil。
func SignDetachedArmored(keyring openpgp.EntityList, plaintext, passphrase []byte) ([]byte, error) {
if len(keyring) == 0 {
return nil, errors.New("gpgx: 密钥环为空,无法签名")
}
copied := cloneKeyRing(keyring)
if err := decryptedPrivateKeys(copied, passphrase); err != nil {
return nil, err
}
var buf bytes.Buffer
if err := openpgp.ArmoredDetachSign(&buf, copied[0], bytes.NewReader(plaintext), nil); err != nil {
return nil, fmt.Errorf("gpgx: 生成分离签名失败: %w", err)
}
return buf.Bytes(), nil
}
// VerifyDetached 校验分离签名(支持 armored 与二进制签名),
// 成功时返回签名者实体。
//
// data 或 signature 任一被篡改、或签名者公钥不在 keyring 中,都会返回 error。
func VerifyDetached(keyring openpgp.EntityList, data, signature []byte) (*openpgp.Entity, error) {
if len(signature) == 0 {
return nil, errors.New("gpgx: 分离签名为空")
}
if len(keyring) == 0 {
return nil, errors.New("gpgx: 密钥环为空,无法验签")
}
signatureReader := bytes.NewReader(signature)
var (
signer *openpgp.Entity
err error
)
if isArmored(signature) {
signer, err = openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewReader(data), signatureReader, nil)
} else {
signer, err = openpgp.CheckDetachedSignature(keyring, bytes.NewReader(data), signatureReader, nil)
}
if err != nil {
return nil, fmt.Errorf("gpgx: 分离签名校验失败: %w", err)
}
if signer == nil {
return nil, errors.New("gpgx: 分离签名校验未返回签名者")
}
return signer, nil
}
// ---------------------------------------------------------------- 密钥生成
// GenerateKeyPair 生成一对 OpenPGP 密钥,返回 ASCII armor 编码的私钥与公钥。
//
// bits 的含义:
// - bits <= 0:生成 Ed25519 + X25519 密钥(速度快,推荐用于测试与快速上手)。
// - bits > 0:生成 RSA 密钥,位数不得小于 MinRSABits(否则返回 error,
// 而不是生成弱密钥)。
//
// passphrase 非空时,私钥会被加密保护;为空则私钥为未加密形式。
// name/email/comment 用于构造 User ID,不得包含 "()<>\x00" 等字符。
func GenerateKeyPair(name, email, comment string, passphrase []byte, bits int) (privateArmored, publicArmored []byte, err error) {
config := &packet.Config{DefaultHash: defaultHashFor(bits)}
switch {
case bits <= 0:
config.Algorithm = packet.PubKeyAlgoEd25519
case bits < MinRSABits:
return nil, nil, fmt.Errorf("gpgx: RSA 位数 %d 过小,至少需要 %d 位", bits, MinRSABits)
default:
config.Algorithm = packet.PubKeyAlgoRSA
config.RSABits = bits
config.MinRSABits = uint16(MinRSABits)
}
entity, err := openpgp.NewEntity(name, comment, email, config)
if err != nil {
return nil, nil, fmt.Errorf("gpgx: 生成密钥失败: %w", err)
}
// 注意顺序:Entity.SerializePrivate 会重新签名 User ID 与子密钥绑定,
// 而签名需要未加密的私钥(否则签名包内部的私钥类型断言会 panic)。
// 因此必须先把私钥写成明文形式,再加密私钥并重新序列化。
privateArmored, err = armorEntity(openpgp.PrivateKeyType, func(w io.Writer) error {
return entity.SerializePrivate(w, config)
})
if err != nil {
return nil, nil, fmt.Errorf("gpgx: 序列化私钥失败: %w", err)
}
if len(passphrase) > 0 {
if err := entity.EncryptPrivateKeys(passphrase, config); err != nil {
return nil, nil, fmt.Errorf("gpgx: 加密私钥失败: %w", err)
}
// 私钥已加密,不能再触发重签名,使用不重签名的序列化路径。
privateArmored, err = armorEntity(openpgp.PrivateKeyType, func(w io.Writer) error {
return entity.SerializePrivateWithoutSigning(w, config)
})
if err != nil {
return nil, nil, fmt.Errorf("gpgx: 序列化加密私钥失败: %w", err)
}
}
publicArmored, err = armorEntity(openpgp.PublicKeyType, func(w io.Writer) error {
return entity.Serialize(w)
})
if err != nil {
return nil, nil, fmt.Errorf("gpgx: 序列化公钥失败: %w", err)
}
return privateArmored, publicArmored, nil
}
// defaultHashFor 依据密钥类型选择哈希:Ed25519 通常搭配 SHA-256,RSA 使用
// SHA-512 以避免与部分实现的自签名哈希偏好冲突。
func defaultHashFor(bits int) crypto.Hash {
if bits <= 0 {
return crypto.SHA256
}
return crypto.SHA512
}
// armorEntity 把 serialize 写出的实体内容包装成 ASCII armor 块。
func armorEntity(blockType string, serialize func(io.Writer) error) ([]byte, error) {
var buf bytes.Buffer
armorWriter, err := armor.Encode(&buf, blockType, nil)
if err != nil {
return nil, err
}
if err := serialize(armorWriter); err != nil {
_ = armorWriter.Close()
return nil, err
}
if err := armorWriter.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// -------------------------------------------------------------------- 工具
// validateRecipients 校验接收方密钥环至少包含一个可用于加密的公钥。
func validateRecipients(recipients openpgp.EntityList) error {
if len(recipients) == 0 {
return errors.New("gpgx: 接收方密钥环为空,无法加密")
}
for _, entity := range recipients {
if entity == nil || entity.PrimaryKey == nil {
return errors.New("gpgx: 接收方密钥环包含空实体")
}
if _, ok := entity.EncryptionKey(time.Now()); ok {
return nil
}
}
return errors.New("gpgx: 接收方密钥环中没有可用的加密密钥(可能已过期或被吊销)")
}
// writeAndClose 写入数据并关闭 WriteCloser,两处错误都会返回。
//
// 加密/签名流的末尾数据(MDC、签名包)只有 Close 时才写出,因此 Close
// 的错误必须检查,否则会静默产生不完整的报文。
func writeAndClose(w io.WriteCloser, data []byte) error {
if _, err := w.Write(data); err != nil {
// 尽力释放内部资源,但以写入错误为准。
_ = w.Close()
return err
}
if err := w.Close(); err != nil {
return err
}
return nil
}
// decryptedPrivateKeys 解开 entities 中所有已加密的私钥副本。
//
// 若 passphrase 为空而私钥已加密,返回明确错误而不是让上层库反复重试。
func decryptedPrivateKeys(entities openpgp.EntityList, passphrase []byte) error {
var encrypted int
for _, entity := range entities {
if entity == nil {
continue
}
encrypted += countEncryptedPrivateKeys(entity)
}
if encrypted == 0 {
// 私钥未加密:忽略传入的口令。
return nil
}
if len(passphrase) == 0 {
return errors.New("gpgx: 私钥已加密,但未提供 passphrase")
}
for _, entity := range entities {
if entity == nil {
continue
}
if err := entity.DecryptPrivateKeys(passphrase); err != nil {
// 不把口令写进错误信息。
return fmt.Errorf("gpgx: 使用给定 passphrase 解开私钥失败: %w", err)
}
}
return nil
}
// countEncryptedPrivateKeys 统计实体中已加密的私钥数量。
func countEncryptedPrivateKeys(entity *openpgp.Entity) int {
var n int
if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
n++
}
for _, subkey := range entity.Subkeys {
if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted {
n++
}
}
return n
}
// oneShotPassphrasePrompt 返回一个最多成功响应一次的口令回调。
//
// openpgp.ReadMessage 在口令不正确时会无限重试 prompt,因此这里在第二次
// 调用时直接返回错误,避免死循环。
func oneShotPassphrasePrompt(passphrase []byte) openpgp.PromptFunction {
used := false
return func(_ []openpgp.Key, _ bool) ([]byte, error) {
if used {
return nil, errors.New("gpgx: passphrase 不正确(已重试)")
}
used = true
// 返回副本,避免上层库持有调用方的切片。
return append([]byte(nil), passphrase...), nil
}
}
// cloneKeyRing 返回 keyring 的独立副本,用于需要解开私钥的操作。
//
// 只有私钥结构会被复制:解密会就地修改 *packet.PrivateKey,复制后调用方
// 传入的密钥环(以及其它并发使用者)不受影响。公钥与签名是只读的,直接共享。
func cloneKeyRing(keyring openpgp.EntityList) openpgp.EntityList {
cloned := make(openpgp.EntityList, 0, len(keyring))
for _, entity := range keyring {
if entity == nil {
continue
}
cloned = append(cloned, cloneEntity(entity))
}
return cloned
}
// cloneEntity 复制实体中可变的私钥材料。
func cloneEntity(entity *openpgp.Entity) *openpgp.Entity {
cloned := *entity
cloned.PrivateKey = clonePrivateKey(entity.PrivateKey)
if len(entity.Subkeys) > 0 {
subkeys := make([]openpgp.Subkey, len(entity.Subkeys))
copy(subkeys, entity.Subkeys)
for i := range subkeys {
subkeys[i].PrivateKey = clonePrivateKey(subkeys[i].PrivateKey)
}
cloned.Subkeys = subkeys
}
return &cloned
}
// clonePrivateKey 浅拷贝私钥结构。
//
// 已加密的私钥清空 PrivateKey 字段,使 *packet.PrivateKey.Decrypt 在副本上
// 重新解析出独立的私钥对象;未加密的私钥保留原有私钥对象指针(只读使用)。
func clonePrivateKey(key *packet.PrivateKey) *packet.PrivateKey {
if key == nil {
return nil
}
cloned := *key
if key.Encrypted {
cloned.PrivateKey = nil
}
return &cloned
}
// readAll 读取全部数据,并保证不会因为上游返回 (0, nil) 而死循环。
func readAll(r io.Reader) ([]byte, error) {
if r == nil {
return nil, errors.New("reader 为 nil")
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
return nil, err
}
return buf.Bytes(), nil
}