Go crypto/ecdsa VerifyASN1 怎样验证 ASN.1 编码签名
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 文本 |
sig | SignASN1 产生的 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 入口;协议如果规定两个整数或固定宽度拼接,则需要明确的编码适配。

不能把 ASN.1 字节按“签名长度除以二”拆成两个整数,也不能只换验签函数来掩盖上游格式不一致。先确认协议写的是哪一种签名表示,再决定使用哪个 API;换 API 不会自动修复错误摘要。
用参数表缩小验签失败范围
| 现象 | 优先检查 | 修复方向 |
|---|---|---|
| 摘要长度是 SHA-256 的两倍 | 是否把十六进制文本当成字节 | 按协议解码为原始摘要 |
| 摘要长度正确但总是 false | 两端消息字节、哈希算法或公钥是否一致 | 比较序列化结果和密钥来源 |
| 签名来自其他语言库 | 对方返回 DER 还是 r、s 拼接格式 | 按协议转换,避免猜测编码 |
| 只在部分消息失败 | 是否对消息做了不一致的规范化 | 固定编码、换行和字段顺序 |
VerifyASN1 能直接接收原文吗?
不能。它接收的是摘要字节;原文需要由应用按照协议计算摘要。
为什么同一私钥两次签名的字节可能不同?
ECDSA 签名包含随机性。排错时应验证签名能否通过,而不是要求两次签名字节完全相同。
应该记录完整签名和摘要吗?
生产日志通常记录输入类型、编码、长度、算法和错误链即可;不要记录私钥,也不要无必要地打印敏感业务消息和完整摘要。
结论很简单:先统一原始消息和摘要,再统一 ASN.1 或整数签名格式,最后确认公钥确实来自对应私钥。这样 VerifyASN1 的布尔结果才有明确的排错含义。
Go os.Root FS 如何把受限目录接入 fs.FS
- 上一篇
- Go os.Root FS 如何把受限目录接入 fs.FS
- 下一篇
- Go mod tidy 为什么 require 块被合并(参数形态)
-
- Golang · Go问答 | 1小时前 |
- Go go fix 为什么 waitgroup 检查器名称改变(参数形态)
- 400浏览 收藏
-
- Golang · Go问答 | 1小时前 |
- Go go-ical 文本中的逗号和换行应该怎样转义
- 212浏览 收藏
-
- Golang · Go问答 | 1小时前 |
- Go archive/tar Reader.Next 怎样读取 PAX 扩展头
- 191浏览 收藏
-
- Golang · Go问答 | 1小时前 | 依赖管理 · go · Go go.mod go mod tidy
- Go mod tidy 为什么 require 块被合并(参数形态)
- 436浏览 收藏
-
- Golang · Go问答 | 1小时前 |
- Go crypto/ecdsa Sign 为什么返回摘要长度错误(参数形态)
- 434浏览 收藏
-
- Golang · Go问答 | 2小时前 |
- Go tls.Config.GetConfigForClient 怎样按请求动态选择 TLS 配置
- 349浏览 收藏
-
- Golang · Go问答 | 2小时前 |
- Go net/http/httptest NewTestServer 怎样配合 testing/synctest 测试内存网络
- 177浏览 收藏
-
- Golang · Go问答 | 2小时前 |
- Go os.Root Chmod 怎样修改受限目录内文件权限
- 416浏览 收藏
-
- Golang · Go问答 | 2小时前 |
- Go Protocompile 编译多文件时怎样取消剩余任务
- 333浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 682次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 698次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 748次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 713次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 551次使用
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- Go JSONJSON null 解码到指针后状态不符合预期怎么办
- 2026-10-18 501浏览
-
- Go crypto/rand.Text 的长度为什么不是固定字符数
- 2026-10-04 501浏览
-
- Go strings.ToValidUTF8 清洗日志内容的边界
- 2026-10-03 501浏览

