新闻中心
Go语言openpgp库用户ID签名无效问题深度解析与解决方案

本文深入探讨go语言中`go.crypto/openpgp`库在进行openpgp用户id签名时生成“坏签名”的问题。核心原因是库内部`signuserid()`实现存在缺陷,错误地使用了密钥签名算法而非用户id签名算法。文章将分析问题根源,并提供解决方案,包括迁移至最新库版本和加强错误处理,以确保生成有效的pgp签名。
Go语言OpenPGP用户ID签名问题概述
在使用Go语言的go.crypto/openpgp库尝试使用私钥签名公钥(特别是其用户ID)时,开发者可能会遇到一个令人困惑的问题:生成的PGP签名在通过GnuPG等外部工具验证时,会被报告为“坏签名”(bad Signature)。这通常意味着签名数据不符合OpenPGP规范,或者使用了错误的算法或数据结构。
以下是一个典型的Go语言实现,旨在从ASCII armored格式的公钥和私钥中读取并进行用户ID签名:
package main
import (
"bytes"
"code.google.com/p/go.crypto/openpgp"
"code.google.com/p/go.crypto/openpgp/armor"
"code.google.com/p/go.crypto/openpgp/packet"
"fmt"
)
// SignPubKeyPKS 接收ASCII armored格式的公钥、私钥和私钥密码,返回ASCII armored格式的已签名公钥。
func SignPubKeyPKS(asciiPub string, asciiPri string, pripwd string) (asciiSignedKey string) {
// 获取私钥实体
_, priEnt := getPri(asciiPri, pripwd)
// 获取公钥实体
_, pubEnt := getPub(asciiPub)
// 提取用户ID字符串
usrIdstring := ""
for _, uIds := range pubEnt.Identities {
usrIdstring = uIds.Name
break // 假设只有一个用户ID或只签名第一个
}
fmt.Println("尝试签名用户ID:", usrIdstring)
// 签名用户ID
errSign := pubEnt.SignIdentity(usrIdstring, &priEnt, nil)
if errSign != nil {
fmt.Println("签名用户ID失败:", errSign.Error())
return ""
}
// 将签名后的公钥实体转换为ASCII armored格式
asciiSignedKey = PubEntToAsciiArmor(pubEnt)
return asciiSignedKey
}
// getPub 从ASCII armored格式字符串中解析出公钥和实体
func getPub(asciiPub string) (pubKey packet.PublicKey, retEntity openpgp.Entity) {
read1 := bytes.NewReader([]byte(asciiPub))
entityList, errReadArm := openpgp.ReadArmoredKeyRing(read1)
if errReadArm != nil {
fmt.Println("读取公钥失败:", errReadArm.Error())
return
}
if len(entityList) == 0 {
fmt.Println("未找到公钥实体")
return
}
for _, pubKeyEntity := range entityList {
if pubKeyEntity.PrimaryKey != nil {
pubKey = *pubKeyEntity.PrimaryKey
retEntity = *pubKeyEntity
break
}
}
return
}
// getPri 从ASCII armored格式字符串中解析出私钥和实体,并解密
func getPri(asciiPri string, pripwd string) (priKey packet.PrivateKey, priEnt openpgp.Entity) {
read1 := bytes.NewReader([]byte(asciiPri))
entityList, errReadArm := openpgp.ReadArmoredKeyRing(read1)
if errReadArm != nil {
fmt.Println("读取私钥失败:", errReadArm.Error())
return
}
if len(entityList) == 0 {
fmt.Println("未找到私钥实体")
return
}
for _, can_pri := range entityList {
if can_pri.PrivateKey == nil {
fmt.Println("实体中不包含私钥")
continue
}
smPr := can_pri.PrivateKey
retEntity := can_pri
priKey = *smPr
errDecr := priKey.Decrypt([]byte(pripwd))
if errDecr != nil {
fmt.Println("解密私钥失败:", errDecr.Error())
return
}
retEntity.PrivateKey = &priKey
priEnt = *retEntity
break // 假设只有一个私钥或只处理第一个
}
return
}
// PubEntToAsciiArmor 将openpgp.Entity转换为ASCII armored格式
func PubEntToAsciiArmor(pubEnt openpgp.Entity) (asciiEntity string) {
gotWriter := bytes.NewBuffer(nil)
wr, errEncode := armor.Encode(gotWriter, openpgp.PublicKeyType, nil)
if errEncode != nil {
fmt.Println("编码Armor失败:", errEncode.Error())
return ""
}
errSerial := pubEnt.Serialize(wr)
if errSerial != nil {
fmt.Println("序列化公钥失败:", errSerial.Error())
}
errClosing := wr.Close()
if errClosing != nil {
fmt.Println("关闭writer失败:", errClosing.Error())
}
asciiEntity = gotWriter.String()
return asciiEntity
}
func main() {
// 示例用法(需要替换为实际的公钥、私钥和密码)
// asciiPublicKey := `-----BEGIN PGP PUBLIC KEY BLOCK-----...`
// asciiPrivateKey := `-----BEGIN PGP PRIVATE KEY BLOCK-----...`
// privateKeyPassword := "your-password"
// signedKey := SignPubKeyPKS(asciiPublicKey, asciiPrivateKey, privateKeyPassword)
// if signedKey != "" {
// fmt.Println("\n--- Signed Public Key ---")
// fmt.Println(signedKey)
// } else {
// fmt.Println("签名失败。")
// }
}
上述代码逻辑上看起来合理,但却无法生成一个被GnuPG认可的有效签名。
问题根源分析:库内部实现缺陷
经过深入调查,发现此问题的根本原因在于code.google.com/p/go.crypto/openpgp库的内部实现缺陷,而非开发者代码逻辑错误。具体来说:
- 错误的签名算法选择:openpgp库中用于签名用户ID的方法(例如Signature.SignUserId()或通过pubEnt.SignIdentity间接调用)错误地使用了用于密钥签名的算法和数据结构。OpenPGP规范中,用户ID签名和密钥签名(用于认证子密钥属于主密钥)是两种不同的操作,需要生成不同类型的签名包(Packet Tag 2, Certification Signature Packet)。
- 不正确的哈希上下文:在验证用户ID签名时,库的PublicKey.VerifyUserIdSignature()方法也存在问题,它没有使用正确的公钥作为哈希上下文,导致只能验证自签名用户ID,而无法正确验证由其他密钥签名的用户ID。
这些问题导致库生成的用户ID签名不符合OpenPGP标准,因此会被GnuPG等严格遵循标准的工具识别为无效。
官方Bug报告与补丁
此问题在Go社区中已被识别并报告。相关的官方Bug报告可以在https://www.php.cn/link/99c3c828637e01c4337451ab836f62ef找到,其中也包含了针对该缺陷的补丁。这表明该问题是openpgp库早期版本的一个已知且已修复的内部错误。
解决方案与最佳实践
鉴于问题根源在于库本身的实现缺陷,解决此问题的关键在于升级和迁移到已修复该bug的库版本。
1. 升级并迁移至最新库版本
code.google.com/p/go.crypto/openpgp是Go语言加密库的旧路径,已被弃用。官方推荐和维护的加密库路径是golang.org/x/crypto/openpgp。
推荐操作步骤:
CA.LA
第一款时尚产品在线设计平台,服装设计系统
94
查看详情
- 更新依赖:将项目中的code.google.com/p/go.crypto/openpgp及相关子包的导入路径全部替换为golang.org/x/crypto/openpgp。
-
获取最新版本:确保您的Go模块依赖已更新到golang.org/x/crypto的最新稳定版本。您可以使用以下命令更新:
go get -u golang.org/x/crypto/openpgp
并确保您的go.mod文件反映了最新的版本。
golang.org/x/crypto/openpgp的最新版本已经包含了对Bug 7371的修复,能够正确生成和验证用户ID签名。迁移后,原有的签名代码(如示例中的SignPubKeyPKS函数)在逻辑上将能正常工作,生成符合OpenPGP规范的有效签名。
2. 增强错误处理
尽管核心问题是库的bug,但在任何生产级代码中,健壮的错误处理都是至关重要的。在上述示例代码中,虽然有一些错误打印,但对于某些关键错误路径(例如getPri或getPub未能找到实体),函数的返回值可能没有被充分利用或导致后续操作空指针。
改进建议:
- 对于可能返回nil或空切片的操作,应在返回前进行显式检查并返回有意义的错误。
- 避免在函数内部直接fmt.Println错误信息并返回空值,而是将错误返回给调用者,由调用者决定如何处理(例如,日志记录、重试、向用户报告)。
- 在处理openpgp.Entity或packet.PrivateKey时,始终检查其是否为nil。
例如,可以修改getPub和getPri函数,使其在失败时返回error:
// getPub 从ASCII armored格式字符串中解析出公钥和实体
func getPub(asciiPub string) (pubKey packet.PublicKey, retEntity openpgp.Entity, err error) {
read1 := bytes.NewReader([]byte(asciiPub))
entityList, errReadArm := openpgp.ReadArmoredKeyRing(read1)
if errReadArm != nil {
return packet.PublicKey{}, openpgp.Entity{}, fmt.Errorf("读取公钥失败: %w", errReadArm)
}
if len(entityList) == 0 {
return packet.PublicKey{}, openpgp.Entity{}, fmt.Errorf("未找到公钥实体")
}
for _, pubKeyEntity := range entityList {
if pubKeyEntity.PrimaryKey != nil {
pubKey = *pubKeyEntity.PrimaryKey
retEntity = *pubKeyEntity
return pubKey, retEntity, nil
}
}
return packet.PublicKey{}, openpgp.Entity{}, fmt.Errorf("未在实体列表中找到主公钥")
}
// getPri 从ASCII armored格式字符串中解析出私钥和实体,并解密
func getPri(asciiPri string, pripwd string) (priKey packet.PrivateKey, priEnt openpgp.Entity, err error) {
read1 := bytes.NewReader([]byte(asciiPri))
entityList, errReadArm := openpgp.ReadArmoredKeyRing(read1)
if errReadArm != nil {
return packet.PrivateKey{}, openpgp.Entity{}, fmt.Errorf("读取私钥失败: %w", errReadArm)
}
if len(entityList) == 0 {
return packet.PrivateKey{}, openpgp.Entity{}, fmt.Errorf("未找到私钥实体")
}
for _, can_pri := range entityList {
if can_pri.PrivateKey == nil {
continue // 跳过没有私钥的实体
}
smPr := can_pri.PrivateKey
retEntity := can_pri
priKey = *smPr
errDecr := priKey.Decrypt([]byte(pripwd))
if errDecr != nil {
return packet.PrivateKey{}, openpgp.Entity{}, fmt.Errorf("解密私钥失败: %w", errDecr)
}
retEntity.PrivateKey = &priKey
priEnt = *retEntity
return priKey, priEnt, nil
}
return packet.PrivateKey{}, openpgp.Entity{}, fmt.Errorf("未在实体列表中找到可用的私钥")
}
// SignPubKeyPKS 签名函数也应返回错误
func SignPubKeyPKS(asciiPub string, asciiPri string, pripwd string) (asciiSignedKey string, err error) {
_, priEnt, err := getPri(asciiPri, pripwd)
if err != nil {
return "", fmt.Errorf("获取私钥失败: %w", err)
}
_, pubEnt, err := getPub(asciiPub)
if err != nil {
return "", fmt.Errorf("获取公钥失败: %w", err)
}
usrIdstring := ""
for _, uIds := range pubEnt.Identities {
usrIdstring = uIds.Name
break
}
if usrIdstring == "" {
return "", fmt.Errorf("公钥实体中未找到用户ID")
}
fmt.Println("尝试签名用户ID:", usrIdstring)
errSign := pubEnt.SignIdentity(usrIdstring, &priEnt, nil)
if errSign != nil {
return "", fmt.Errorf("签名用户ID失败: %w", errSign)
}
asciiSignedKey, err = PubEntToAsciiArmor(pubEnt)
if err != nil {
return "", fmt.Errorf("转换为ASCII Armor格式失败: %w", err)
}
return asciiSignedKey, nil
}
// PubEntToAsciiArmor 转换函数也应返回错误
func PubEntToAsciiArmor(pubEnt openpgp.Entity) (asciiEntity string, err error) {
gotWriter := bytes.NewBuffer(nil)
wr, errEncode := armor.Encode(gotWriter, openpgp.PublicKeyType, nil)
if errEncode != nil {
return "", fmt.Errorf("编码Armor失败: %w", errEncode)
}
errSerial := pubEnt.Serialize(wr)
if errSerial != nil {
return "", fmt.Errorf("序列化公钥失败: %w", errSerial)
}
errClosing := wr.Close()
if errClosing != nil {
return "", fmt.Errorf("关闭writer失败: %w", errClosing)
}
return gotWriter.String(), nil
}3. 验证签名有效性
即使代码逻辑和库版本都已正确,在涉及加密操作时,始终建议进行严格的端到端测试。使用GnuPG等外部工具验证Go程序生成的PGP签名,是确保其符合标准和互操作性的最佳实践。
# 假设您已将Go程序生成的签名公钥保存到 signed_public_key.asc gpg --check-sigs signed_public_key.asc
如果签名有效,GnuPG将显示正确的签名信息,而不会报告“bad Signature”。
总结
Go语言openpgp库在早期版本中存在一个关键缺陷,导致其生成的用户ID签名被GnuPG等工具判定为无效。该问题源于库内部对OpenPGP规范的错误实现,尤其是在签名算法和哈希上下文的处理上。
解决此问题的核心在于:
- 迁移并升级:将项目中的code.google.com/p/go.crypto/openpgp依赖更新为golang.org/x/crypto/openpgp的最新稳定版本,该版本已包含了对相关bug的修复。
- 强化错误处理:在Go代码中实现更健壮的错误检查和传播机制,以提高程序的稳定性和可维护性。
- 外部验证:始终使用G
以上就是Go语言openpgp库用户ID签名无效问题深度解析与解决方案的详细内容,更多请关注其它相关文章!
# 未找到
# 谷歌seo综合查询网
# 谢家湾网站推广方式
# 浙江抖音seo机构排名
# 济宁抖音seo品牌
# 如何推广新生儿网站
# 西藏企业网站优化
# 韩都衣舍 推广营销方法
# 安义seo价钱
# 浙江邮件推广网站大全
# 新民推广网站建设代理商
# 只有一个
# 问题是
# 已被
# 第一个
# 您的
# word
# 数据结构
# 转换为
# 文档
# 公钥
# igs
# crypto
# red
# google
# ai
# iis
# 工具
# 编码
# go语言
# golang
# go
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
俄罗斯Yandex免登录入口_Yandex搜索引擎官网一键直达
必由学官网快捷入口 必由学网页版在线学习平台
c++ 命名空间怎么用 c++ namespace使用指南
Mac终端命令大全_Mac常用Terminal指令速查
韩剧圈正版入口页面_韩剧圈官网登录链接
怎样把文件彻底粉碎无法恢复_Windows下安全删除敏感数据【隐私保护】
蛙漫移动版在线看 蛙漫手机浏览器直达入口
Go语言中动态执行代码字符串的策略与实践
Win11怎么查看显卡显存 Win11显示适配器属性及专用视频内存查询
J*aScriptWebpack优化_J*aScript构建工具实战
163邮箱注册官网 免费申请163个人邮箱
J*aScript中在Map循环中检测并处理空数组元素
知音漫客正版漫画平台_知音漫客官网账号登录
优化 Python 函数中的条件逻辑:解决 if-else 嵌套与参数选择问题
Steam官网入口直达 Steam注册及登录步骤
知音漫客官网漫画下载_知音漫客网页版阅读记录
Node.js 中使用 node-cron 实现定时 API 数据抓取与处理
期待已久:小米17 Ultra、小米首款NAS本月登场
必由学官方网站入口 必由学学生教师共用登录通道
在J*a中如何捕获IndexOutOfBoundsException_索引越界异常防护方法说明
高德地图总提示网络异常怎么办 高德地图离线导航设置与网络排查方法
《噬血代码2》新预告片发布 展示游戏剧情
响应式容器内容自动缩放与宽高比维持教程
如何仅使用CSS更改登录界面背景图像图标的颜色
Go语言中高效处理x-www-form-urlencoded表单数据
age动漫网站入口 age动漫官网直接访问入口
J*a递归快速排序中静态变量导致数据累积问题的解决方案
Go调试环境为何无法启动_Go调试器启动失败原因与解决策略
邮政快递包裹最新位置 邮政快递实时追踪入口
随机参数递归函数的基准调用次数与时间复杂度探究
vivo浏览器自带的下载器速度慢怎么办 vivo浏览器提升文件下载速度的技巧
c++如何使用折叠表达式(Fold Expressions)_c++17可变参数模板新技巧
钉钉视频会议声音异常如何处理 钉钉会议音频修复技巧
“在文档元素之后找到了标记”是什么错误? 检查并修复XML中多个根元素的3个方法
漫蛙MANWA漫画主页官方入口 漫蛙漫画最新在线阅读地址
必由学官方平台入口 必由学在线课堂登录地址
J*a TimerTask中HashMap意外清空的深层原因与解决方案
谷歌浏览器最新官方入口链接 谷歌浏览器网页版官网导航
React中useState与局部变量:理解组件状态管理与渲染机制
谷歌google账号注册详细步骤 谷歌账号注册官方教程
谷歌浏览器一键优化方案_谷歌浏览器直达主页极速不卡版
快手赚钱渠道_快手收益来源
Descript怎样用AI剪辑自动去噪_Descript用AI剪辑自动去噪【自动降噪】
学习通在线学习平台 学习通网页版直接进入课程中心
处理动态列数据:J*a ArrayList的正确初始化与字符累加教程
在Runstone环境中高效处理TasteDive API的JSON数据
妖精动漫免费平台 妖精动漫官网资源观看网址
Linux如何构建多环境配置管理_Linux多环境配置方案
邮政快递单号查询入口 邮政快递物流信息在线查询入口
PDF文件体积过大处理_PDF压缩技巧详解


2025-11-21
浏览次数:次
返回列表