新闻中心

Golang openpgp 库中 GPG 密钥签名错误分析与解决方案

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

Golang openpgp 库中 GPG 密钥签名错误分析与解决方案

本文深入探讨了使用 go 语言 `go.crypto/openpgp` 库进行 gpg 公钥签名时可能遇到的“签名无效”问题。核心原因在于该库早期版本中 `signidentity` 方法的实现缺陷,它错误地使用了子密钥签名算法而非用户id签名算法。文章将分析此问题,并强调使用最新 `golang.org/x/crypto/openpgp` 库的重要性,以确保加密操作的正确性和安全性。

引言:GPG 密钥签名在 Go 中的挑战

GPG(GNU Privacy Guard)签名是确保数据完整性、来源真实性和不可否认性的重要手段。在 Go 语言中,openpgp 库提供了实现 GPG 加密和签名的能力。然而,开发者在使用该库进行公钥签名时,可能会遇到生成的签名在外部 GPG 工具(如 gpg --check-sigs)验证时显示为“bad Signature”的问题。这通常不是因为代码逻辑上的明显错误,而是由于库底层实现的特定缺陷。

问题复现:早期 openpgp 库的签名逻辑

为了理解这个问题,我们首先来看一个典型的 Go 语言中用于签名公钥用户 ID 的代码结构。以下示例展示了如何加载公钥和私钥实体,然后使用私钥对公钥的用户 ID 进行签名:

CA.LA CA.LA

第一款时尚产品在线设计平台,服装设计系统

CA.LA 94 查看详情 CA.LA
package main

import (
    "bytes"
    "fmt"
    "io"

    "golang.org/x/crypto/openpgp"
    "golang.org/x/crypto/openpgp/armor"
    "golang.org/x/crypto/openpgp/packet"
)

// LoadEntityFromArmoredKeyRing 从 ASCII Armored 字符串加载 openpgp.Entity
// password 参数仅在加载私钥且私钥加密时需要
func LoadEntityFromArmoredKeyRing(armoredKey string, password []byte) (*openpgp.Entity, error) {
    keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(armoredKey)))
    if err != nil {
        return nil, fmt.Errorf("读取 Armored 密钥环失败: %w", err)
    }
    if len(keyring) == 0 {
        return nil, fmt.Errorf("未找到密钥实体")
    }

    entity := keyring[0] // 假设密钥环中只有一个实体

    // 如果有私钥且需要解密
    if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
        if password == nil {
            return nil, fmt.Errorf("私钥已加密,但未提供密码")
        }
        if err := entity.PrivateKey.Decrypt(password); err != nil {
            return nil, fmt.Errorf("解密私钥失败: %w", err)
        }
    }
    return entity, nil
}

// SignPublicKeyWithPrivateKey 使用私钥签名公钥的用户ID
// 此函数演示了签名的核心逻辑,并强调了可能出现问题的 SignIdentity 调用
func SignPublicKeyWithPrivateKey(
    armoredPublicKey string,
    armoredPrivateKey string,
    privateKeyPassword string,
) (string, error) {
    // 1. 加载公钥实体
    pubEntity, err := LoadEntityFromArmoredKeyRing(armoredPublicKey, nil)
    if err != nil {
        return "", fmt.Errorf("加载公钥实体失败: %w", err)
    }

    // 2. 加载私钥实体并解密
    priEntity, err := LoadEntityFromArmoredKeyRing(armoredPrivateKey, []byte(privateKeyPassword))
    if err != nil {
        return "", fmt.Errorf("加载私钥实体失败: %w", err)
    }

    // 3. 获取公钥的用户ID
    var userIdName string
    for _, identity := range pubEntity.Identities {
        userIdName = identity.Name
        break // 通常取第一个用户ID进行签名
    }
    if userIdName == "" {
        return "", fmt.Errorf("公钥实体中未找到用户ID")
    }

    fmt.Printf("正在使用私钥 '%s' 签名公钥 '%s' 的用户ID '%s'\n", priEntity.PrimaryKey.KeyIdString(), pubEntity.PrimaryKey.KeyIdString(), userIdName)

    // 4. 执行签名操作
    // 核心问题曾发生在此处:早期版本的 openpgp 库在此方法中存在实现缺陷
    err = pubEntity.SignIdentity(userIdName, priEntity, nil)
    if err != nil {
        return "", fmt.Errorf("签名用户ID失败: %w", err)
    }
    fmt.Println("用户ID签名成功。")

    // 5. 将签名的公钥实体序列化为 ASCII Armored 字符串
    buf := new(bytes.Buffer)
    writer, err := armor.Encode(buf, openpgp.PublicKeyType, nil)
    if err != nil {
        return "", fmt.Errorf("创建 Armored 编码器失败: %w", err)
    }
    if err := pubEntity.Serialize(writer); err != nil {
        return "", fmt.Errorf("序列化签名的公钥实体失败: %w", err)
    }
    if err := writer.Close(); err != nil

以上就是Golang openpgp 库中 GPG 密钥签名错误分析与解决方案的详细内容,更多请关注其它相关文章!


# 环中  # 陕西专用网站推广哪家强  # 福州seo网络营销  # php加seo  # SEO基础舞蹈风暴  # 嘉兴网站关键词优化排名  # 江门如何用seo  # 厦门网络营销推广策划  # 网站代码优化的标准  # 品牌营销推广速来火5星  # 品质网站建设推广费用  # 相关文章  # 这个问题  # 在此  # 第一个  # word  # 库中  # 转换为  # 加载  # 文档  # 公钥  # igs  # crypto  # red  # ai  # 工具  # 编码  # golang  # go 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: 荣耀Play7TPro怎样在信息App置顶客服对话_iPhone荣耀Play7TPro信息App置顶客服对话【优先查看】  京东京造J1和网易云音乐氧气真无线有什么不同_国产电商蓝牙耳机音质对比  windows10怎么关闭系统提示音_windows10彻底静音设置方法  KFC早餐时段怎么领特惠代码_KFC早餐订餐优惠代码获取与使用说明  2026春节假期时间安排 2026春节假日查询  Sublime怎么配置Nim语言环境_Sublime Nim代码高亮与补全  c++如何使用TBB库进行任务并行_c++ Intel线程构建模块  解决 MongoDB 聚合查询中对象数组 _id 匹配问题  LINUX的I/O重定向是什么_深入理解LINUX中 >、>> 与 < 的区别  Lar*el的路由模型绑定怎么用_Lar*el Route Model Binding简化控制器逻辑  C++ string find函数返回值npos详解_C++字符串查找失败的判断条件  AO3访问入口汇总 AO3网页版同人作品一键直达  神经网络二分类模型训练异常:高损失与完美验证准确率的排查与修正  Excel组合图表怎么做 Excel创建柱状图与折线组合图教程【图表】  Win11截图该按哪些键 Win11截屏完整流程解析【教程】  sublime怎么覆盖插件的默认快捷键_sublime快捷键优先级与设置  汽水音乐在线版入口_汽水音乐网页播放手册  德邦快递查询平台 德邦快递物流信息查询入口  C++如何比较两个字符串_C++ string compare函数与操作符对比  192.168.1.1管理中心入口 192.168.1.1路由器网页设置平台  不同用户不同价格! 索尼开启账户个性化定价测试  Python:递归比较文件夹内容并找出特定类型文件的差异  J*aScript中如何高效提取对象指定属性  如何创建没有密码的Windows本地账户_跳过微软账户登录的技巧【教程】  Win10文件资源管理器“此电脑”分组怎么关 Win10恢复经典视图【技巧】  聚水潭ERP登录页面入口 聚水潭ERP官网登录界面  响应式CSS Grid布局:优化网格项在小屏幕下的堆叠与宽度适配  火锅吃太多会怎样 火锅吃太多会上火吗  微博网页版主页入口 微博官方网站免登录访问  J*aScript对象创建方式_J*aScript设计模式应用  深入理解J*aScript Promise异步执行与微任务队列  2026年发布! 美少女养成动作RPG《神剑少女战记》发布实机演示  蛙漫漫画官网在线入口 蛙漫全本漫画免费阅读平台  PrimeNG Sidebar背景色自定义指南:CSS覆盖与主题化实践  yandex入口引擎手机版 yandex安卓版下载入口  Mac怎么查看崩溃日志_Mac控制台错误报告分析  Python中如何避免重复条件判断:利用数据结构实现动态逻辑  支付宝解绑银行卡步骤_支付宝如何解除绑定银行卡  TypeScript/J*aScript:高效查找数组中首个唯一ID对象  AI泡沫首次被“刺破”:GPU十年都无法存活!  三星GalaxyZFold5怎样在相册制作折叠屏分镜_iPhone三星GalaxyZFold5相册制作折叠屏分镜【创意编辑】  可靠CSGO开箱平台解析 CSGO开箱网合集  谷歌邮箱网页版官方页面入口 谷歌邮箱网页端快速访问  在WordPress中通过REST API获取BasicAuth保护的远程文章  c++如何使用Catch2编写单元测试_c++简洁易用的BDD风格测试框架  Mudbox图层蒙版怎么用_Mudbox图层蒙版数字雕刻应用技巧  J*aScript map 迭代中检测空数组元素的有效方法  Android Studio计算器C键逻辑错误排查与修复:条件判断优化指南  C++如何进行游戏物理模拟_使用Box2D库为C++游戏添加2D物理效果  新三国志曹操传110级星符试炼夏侯渊极难攻略 

搜索