当前位置:首页 > 文章列表 > Golang > Go问答 > Go crypto/ecdsa VerifyASN1 怎样验证 ASN.1 编码签名

Go crypto/ecdsa VerifyASN1 怎样验证 ASN.1 编码签名

来源:17golang原创 2026-10-25 10:01:54 0浏览 收藏

Go 里用 crypto/ecdsa.VerifyASN1 验证签名时,最容易弄错的不是函数名,而是三个输入的形态:公钥必须对应签名私钥,hash 必须是双方约定的原始摘要字节,sig 必须是 ASN.1 编码的签名字节。只要其中一个被换成十六进制文本、另一份消息的摘要,或者固定宽度的 r||s,返回值就会是 false。

官方资料入口:https://pkg.go.dev/crypto/ecdsa;实现源码:https://go.dev/src/crypto/ecdsa/ecdsa.go。本文只讨论 VerifyASN1 的输入契约、签名格式和排错路径,不展开证书链验证或密钥生成。

最稳妥的配对是:同一份原始消息按同一算法计算摘要,签名端用 SignASN1 产生 ASN.1 字节,验签端把对应公钥、同一摘要和完整签名字节交给 VerifyASN1。

先固定 VerifyASN1 的三个输入

VerifyASN1 的签名是 func VerifyASN1(pub *PublicKey, hash, sig []byte) bool。它不接收原文,也不负责替你选择哈希算法。pub 是验证用公钥,hash 是已经算好的摘要,sig 是 ASN.1 编码后的签名。三个参数都来自协议约定,而不是从签名长度临时猜出来。

输入应该是什么常见误传
pub与签名私钥匹配的 ECDSA 公钥另一把钥匙或错误曲线的公钥
hash原始摘要字节,例如 SHA-256 的 32 字节结果消息正文、十六进制文本、Base64 文本
sigSignASN1 产生的 ASN.1 签名字节两个整数直接拼接的固定宽度字节
原始消息、摘要、公钥和 ASN.1 签名进入 VerifyASN1 的静态契约说明图
图1:VerifyASN1 输入契约说明图,展示公钥、原始摘要和 ASN.1 签名字节之间的静态关系,不是运行截图或执行流程。

因此,验签失败时先把这三个值分别记录为“来源、编码、长度”,再看密码学结论。长度相同不代表内容相同;一份错误的 32 字节摘要仍然会得到失败结果。

区分原始摘要与十六进制文本

以 SHA-256 为例,sha256.Sum256 返回的是 32 字节数组。把它格式化成十六进制后,通常得到 64 个 ASCII 字符;把字符串直接转换为 []byte,得到的是这 64 个字符的字节,不是原来的 32 字节摘要。Base64 也一样:它适合传输和展示,不能未经解码直接交给验签函数。

正确的边界是:如果协议传来原始摘要,直接使用其字节;如果协议传来十六进制或 Base64,先按协议指定的编码解码,并把解码错误返回给调用方。不要通过截断文本或反复尝试不同解码方式来“凑长度”,那会把输入契约问题变成更难定位的验签失败。

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func main() {
    message := []byte("order=demo-2026")
    digest := sha256.Sum256(message)

    // digest[:] 是 32 字节原始摘要,适合交给 VerifyASN1。
    raw := digest[:]
    // hexText 只是展示或传输格式,长度通常是原始摘要的两倍。
    hexText := hex.EncodeToString(raw)
    decoded, err := hex.DecodeString(hexText)
    if err != nil {
        // 解码失败必须中止验签,不能用原文本继续尝试。
        panic(err)
    }
    fmt.Println(len(raw), len(hexText), len(decoded))
}

这个片段只说明字节形态,未把输出当作本机运行结果。实际接入时,raw 和 decoded 才可能作为摘要输入;hexText 不应直接传入。

让 SignASN1 与 VerifyASN1 使用同一约定

签名和验签必须对同一份消息字节计算同一种摘要。JSON 字段顺序、字符编码、换行和空格只要有一个不同,两个摘要就不同。推荐把摘要算法固定在业务函数里,让调用方传入原文或已经明确标注的摘要,不要让调用方同时自由传入算法名和任意字节。

func signAndVerify(key *ecdsa.PrivateKey, payload []byte) (bool, error) {
    digest := sha256.Sum256(payload)

    // SignASN1 返回与 VerifyASN1 配套的 ASN.1 签名字节。
    sig, err := ecdsa.SignASN1(rand.Reader, key, digest[:])
    if err != nil {
        return false, fmt.Errorf("生成 ASN.1 签名失败: %w", err)
    }

    // 验签重新计算同一消息的摘要,并使用对应公钥。
    valid := ecdsa.VerifyASN1(&key.PublicKey, digest[:], sig)
    return valid, nil
}

上面示例中的 ecdsa、sha256、rand 和 fmt 需要按正常 Go 文件导入。这里关注的是参数契约:SignASN1 和 VerifyASN1 共用原始摘要与 ASN.1 输出格式。示例不提供实测输出,也不把静态说明图当作运行证据。

按协议选择签名入口与输出格式

Go 同时提供 ecdsa.Sign、ecdsa.SignASN1 和 VerifyASN1。包级 Sign 返回两个大整数 r、s;SignASN1 返回 ASN.1 编码字节。协议如果规定 ASN.1 DER,就应让签名端和验签端都使用 ASN.1 入口;协议如果规定两个整数或固定宽度拼接,则需要明确的编码适配。

SignASN1 与 VerifyASN1 的 ASN.1 域和 ecdsa.Sign 整数域静态边界结构图
图2:签名格式边界结构图,对比 ASN.1 DER 与 r、s 整数两种静态输出关系,不是运行截图或性能证据。

不能把 ASN.1 字节按“签名长度除以二”拆成两个整数,也不能只换验签函数来掩盖上游格式不一致。先确认协议写的是哪一种签名表示,再决定使用哪个 API;换 API 不会自动修复错误摘要。

用参数表缩小验签失败范围

现象优先检查修复方向
摘要长度是 SHA-256 的两倍是否把十六进制文本当成字节按协议解码为原始摘要
摘要长度正确但总是 false两端消息字节、哈希算法或公钥是否一致比较序列化结果和密钥来源
签名来自其他语言库对方返回 DER 还是 r、s 拼接格式按协议转换,避免猜测编码
只在部分消息失败是否对消息做了不一致的规范化固定编码、换行和字段顺序

VerifyASN1 能直接接收原文吗?

不能。它接收的是摘要字节;原文需要由应用按照协议计算摘要。

为什么同一私钥两次签名的字节可能不同?

ECDSA 签名包含随机性。排错时应验证签名能否通过,而不是要求两次签名字节完全相同。

应该记录完整签名和摘要吗?

生产日志通常记录输入类型、编码、长度、算法和错误链即可;不要记录私钥,也不要无必要地打印敏感业务消息和完整摘要。

结论很简单:先统一原始消息和摘要,再统一 ASN.1 或整数签名格式,最后确认公钥确实来自对应私钥。这样 VerifyASN1 的布尔结果才有明确的排错含义。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go os.Root FS 如何把受限目录接入 fs.FSGo os.Root FS 如何把受限目录接入 fs.FS
上一篇
Go os.Root FS 如何把受限目录接入 fs.FS
Go mod tidy 为什么 require 块被合并(参数形态)
下一篇
Go mod tidy 为什么 require 块被合并(参数形态)
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    682次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    698次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    748次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    713次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    551次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码