当前位置:首页 > 文章列表 > Golang > Go问答 > Go crypto/ecdsa Sign 为什么返回摘要长度错误(参数形态)

Go crypto/ecdsa Sign 为什么返回摘要长度错误(参数形态)

来源:17golang原创 2026-10-25 09:52:37 0浏览 收藏

Go 的 ECDSA 签名出现 hash length does not match hash function 时,先看实际调用的函数。当前官方源码中,(*ecdsa.PrivateKey).Sign 会把 len(digest) 与 opts.HashFunc().Size() 比较:使用 SHA-256 选项,就应传入 32 字节原始摘要。把十六进制字符串强转成字节、误传消息正文,或把 SHA-512 摘要配给 SHA-256,都会破坏这个约定。

本文围绕一个订单签名函数比较参数形态与 API 选择,示例依据当前官方资料编写,未在本机执行。官方入口可复制:https://pkg.go.dev/crypto/ecdsa;长度检查实现:https://go.dev/src/crypto/ecdsa/ecdsa.go。遇到不同 Go 版本或远程签名器,应以实际实现的行为为准。

先确认调用的是哪一种 Sign

项目里一行 signer.Sign(...) 不一定直接对应包级 ecdsa.Sign。先定位变量的具体类型,再观察参数列表和返回值。三个入口接收的都是已经计算好的摘要,但接口约定不同:

入口摘要算法选项签名形态适用约束
ecdsa.Sign没有 opts 参数r、s 两个整数协议明确要求自行处理整数
ecdsa.SignASN1没有 opts 参数ASN.1 字节串应用直接采用 ASN.1 签名
私钥 Sign 方法crypto.SignerOptsASN.1 字节串使用 crypto.Signer 接口或明确声明摘要算法

“摘要长度不匹配”首先是方法参数契约问题。不能把它解释成私钥必须换一条曲线,也不能只修改签名输出编码来消除它。若错误被服务层包装,应保留底层错误链以及实际 Go 版本,判断是标准库、适配器还是外部签名服务返回了错误。

比较原始摘要、十六进制和 Base64 参数

同一份 SHA-256 摘要有多种表达:原始摘要是 32 字节,十六进制文本通常是 64 个 ASCII 字符,带标准填充的 Base64 文本是 44 个 ASCII 字符。后两者适合日志或传输,不能直接当原始摘要使用。[]byte(hexText) 得到的是文本字符字节,转换类型并没有完成解码。

例如 sha256.Sum256(payload) 返回固定数组,digest[:] 才是传给签名接口的原始字节切片。如果接入协议已经提供摘要文本,先按协议指定的编码解码,并处理解码错误;如果协议提供原始消息,则在应用边界计算约定的摘要。不要“尝试每种解码直到不报错”,这会隐藏输入契约错误。

原始摘要与两种文本编码的静态关系说明
图1:参数形态说明图。摘要分组与文本表示分组之间的连线表示编码关系,不是运行流程;签名需要的是原始摘要字节。

也不要把 64 字节的十六进制文本裁成前 32 字节来凑长度。这样的参数可能通过长度判断,却不再表示双方约定的摘要,问题会从“签名报错”变成“对端验签失败”。长度相等只能说明形态可能正确,不能证明内容正确。

把摘要算法和 SignerOpts 配成一对

在本例中,摘要值由 sha256.Sum256 计算,算法选项使用 crypto.SHA256。这两个选择应放在同一个业务函数中,避免调用方随意组合。SHA-256、SHA-384、SHA-512 的完整摘要分别是 32、48、64 字节;只有字节数和算法声明都对应,才符合接口契约。

长度检查不会重新计算消息摘要。例如 SHA-256 和另一种同长度算法产生的字节串,单靠长度不能辨别。原始消息采用什么编码、JSON 是否做规范化、是否包含换行,都必须由协议约定。签名侧和验签侧各自序列化一个对象,不能保证得到相同字节。

曲线与摘要长度也不是同一个维度。P-256 描述椭圆曲线参数,SHA-256 描述消息摘要算法;不要从曲线名字推导出“任何输入只要补齐 32 字节就能签”。对于较长摘要,底层 ECDSA 运算存在按曲线阶位数截断的规则,但调用方法时仍应传入与 opts 对应的完整摘要,不应先手动裁剪。

私钥签名方法的摘要与算法选项及输出对象关系
图2:签名接口结构说明图。方法与摘要、算法选项、私钥及 ASN.1 签名存在静态参数或返回关系,公钥与验签接口对应签名格式。

用完整示例固定签名与验签约定

下面的原创示例把消息摘要和 SignerOpts 固定在一个函数里,使用随机化签名入口。代码没有把摘要先转成文本,也没有让调用方传入彼此独立的算法编号与任意字节切片。临时密钥只服务示例;真实项目应通过已有密钥管理方式取得私钥。

package main

import (
    "crypto"
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/sha256"
    "fmt"
)

// signPayload 为演示协议固定 SHA-256,返回 ASN.1 签名。
func signPayload(key *ecdsa.PrivateKey, payload []byte) ([]byte, error) {
    digest := sha256.Sum256(payload)
    // digest[:] 是原始字节,crypto.SHA256 声明相同算法。
    sig, err := key.Sign(rand.Reader, digest[:], crypto.SHA256)
    if err != nil {
        return nil, fmt.Errorf("签名失败: %w", err)
    }
    return sig, nil
}

func main() {
    // 临时密钥仅用于独立示例,生产系统应读取受控密钥。
    key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    if err != nil {
        panic(err)
    }
    payload := []byte("order=demo-2026;amount=128")
    sig, err := signPayload(key, payload)
    if err != nil {
        panic(err)
    }
    // 验签采用完全相同的消息字节、摘要算法和 ASN.1 格式。
    digest := sha256.Sum256(payload)
    valid := ecdsa.VerifyASN1(&key.PublicKey, digest[:], sig)
    fmt.Println("验签是否通过:", valid)
}

示例的判断目标是签名错误被完整返回,并且对同一消息重新计算摘要后,VerifyASN1 返回真;这里没有提供实测输出。随机化签名不应靠比较两次签名字节完全一致来判断正确性。若业务要求确定性签名,需单独确认 Go 版本对 nil 随机参数的约定,并保证哈希选项和摘要一致,不能把 nil 当成普通的“缺省随机源”。

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

如果代码本来使用 crypto.Signer 抽象,优先修复摘要参数和 opts,不要为了绕过错误改成另一个入口。直接使用 ECDSA 且协议采用 ASN.1 签名时,SignASN1 使返回格式更明确;但它没有 opts 参数,应用自己仍要承担摘要算法一致性的责任。改 API 不会自动修复被编码成文本的摘要。

只有在协议确实定义了两个整数或固定宽度拼接格式时,才考虑包级 ecdsa.Sign。ASN.1 签名不能直接作为固定宽度的 r || s 使用,也不能仅凭签名长度决定拆分位置。本文示例选择 ASN.1,是为了使签名与 VerifyASN1 的输入契约一致;具体协议若要求其他格式,应按该协议另写明确的编码适配。

排错决策表与常见疑问

观察到的参数优先判断应采取的修复
SHA-256 选项,64 字节 ASCII 摘要是否传了十六进制文本按既定编码解码成原始摘要
SHA-256 选项,64 字节二进制摘要是否实际计算了 SHA-512回到协议统一算法与选项
长度正确但对端拒绝消息字节、算法、密钥或签名格式不一致比较两端约定与公开测试向量
调用包级 Sign 仍看到相同错误文字适配层或其他实现的包装错误定位实际调用栈与具体实现

能传 crypto.Hash(0) 表示直接签原文吗?

不要这样使用 ECDSA 私钥方法。当前官方实现明确拒绝零哈希标识;本例始终传入预先计算的 SHA-256 摘要与对应选项。

把摘要再哈希一次能修复长度吗?

不应为了凑长度额外哈希。如果协议要求一次 SHA-256,而应用计算两次,即使长度正确也改变了被签名内容。只有协议明确约定双重摘要时才这样处理。

日志里应该打印什么?

记录具体签名器类型、算法标识、输入长度、编码种类及错误链,避免打印私钥、敏感消息正文和完整业务摘要。长度与类型通常已经足够区分这类形态错误。

参考资料:ECDSA 标准库文档 https://pkg.go.dev/crypto/ecdsa;官方实现 https://go.dev/src/crypto/ecdsa/ecdsa.go。本文将入口、参数与格式分开比较,最终修复应落在双方一致的原始消息与摘要约定上。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go tls.Config.GetConfigForClient 怎样按请求动态选择 TLS 配置Go tls.Config.GetConfigForClient 怎样按请求动态选择 TLS 配置
上一篇
Go tls.Config.GetConfigForClient 怎样按请求动态选择 TLS 配置
Go os.Root Remove 怎样删除受限目录内文件
下一篇
Go os.Root Remove 怎样删除受限目录内文件
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    681次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    698次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    745次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    713次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    551次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码