登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

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

来源:17golang原创

时间:2026-10-25 09:52:37 434浏览 收藏

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删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>