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

Go Ed25519 验签失败时怎样核对消息和公钥

来源:17golang原创

时间:2026-10-17 20:32:39 368浏览 收藏

Go 里 Ed25519 验签失败,最先要查的不是换算法,而是三个输入是否仍然是签名时的同一组字节:message 有没有被重新拼接,publicKey 有没有取错,signature 有没有把 Base64 或十六进制文本直接传进来。只要其中一项变化,ed25519.Verify 就会返回 false。

官方地址:https://pkg.go.dev/crypto/ed25519

排查顺序固定为“原始消息字节 → 公钥来源与长度 → 签名解码与长度 → 再调用 Verify”。公钥应为 32 字节,标准 Ed25519 签名应为 64 字节;长度不对时不要继续猜参数。

先确认 Verify 的三个输入没有被文本化

Verify(publicKey, message, sig) 比较的是签名者实际签过的字节。比如发送端签的是带换行的 JSON,接收端用 strings.TrimSpace 去掉换行,或者重新序列化 JSON 改变字段顺序,验签都会失败。中文、空格、\n 和 UTF-8 编码也属于消息的一部分。

// 这个示例把签名和验签明确绑定到同一份原始字节。
package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "fmt"
)

func main() {
    // 使用随机源生成一对匹配的公钥和私钥。
    publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
    if err != nil {
        panic(err) // 密钥生成失败时不继续产生不可复查的签名。
    }

    message := []byte("order=1001&amount=58.00\n")
    signature := ed25519.Sign(privateKey, message)
    ok := ed25519.Verify(publicKey, message, signature)
    fmt.Println(ok) // 只有 message 的字节序列保持不变才会得到 true。
}

如果消息来自 HTTP、JSON 或消息队列,建议在签名协议里明确“签名原文”是规范化前还是规范化后内容,并在两端记录长度和摘要,而不是记录整段敏感正文。

Ed25519 验签输入边界静态技术框图
消息、公钥和签名分别进入 Verify;排查时先看三个输入的字节边界是否一致。

用长度检查排除公钥和签名截断

标准库给出了明确的尺寸常量:ed25519.PublicKeySize 是 32,ed25519.SignatureSize 是 64。公钥长度不符合要求时,当前 Go 实现的 Verify 可能直接触发 panic,而签名长度错误会返回失败。因此,外部输入进入验签函数前应先做长度检查。

// 先检查固定长度,避免把传输截断误判成算法问题。
func verifyEd25519(publicKey, message, signature []byte) (bool, string) {
    if len(publicKey) != ed25519.PublicKeySize {
        return false, "public key length mismatch"
    }
    if len(signature) != ed25519.SignatureSize {
        return false, "signature length mismatch"
    }
    // 长度通过后才调用 Verify,避免错误公钥进入底层实现。
    return ed25519.Verify(ed25519.PublicKey(publicKey), message, signature), "verified"
}

实际排查中,长度常见异常包括:把 32 字节公钥的十六进制文本当成 64 字节公钥、Base64 解码失败后仍使用空切片、签名字段只取了前 32 字节,以及把带填充的编码字符串误截断。

对齐 Base64、十六进制与换行处理

公钥和签名经常以 Base64 或十六进制传输。验签需要的是解码后的字节,不是编码后的 ASCII 字符串。编码方式必须由协议固定,不能在失败后轮换尝试并把某次成功当成正确配置;同时要把解码错误单独记录。

// decodeSignature 只负责把协议字段还原为签名字节。
func decodeSignature(encoded string) ([]byte, error) {
    // 这里假定协议使用标准 Base64;不要静默改用 RawStdEncoding。
    raw, err := base64.StdEncoding.DecodeString(encoded)
    if err != nil {
        return nil, fmt.Errorf("decode signature base64: %w", err)
    }
    if len(raw) != ed25519.SignatureSize {
        return nil, fmt.Errorf("decoded signature has %d bytes", len(raw))
    }
    return raw, nil
}

上面代码需要导入 encoding/base64 和 fmt。如果协议使用十六进制,就改用 encoding/hex.DecodeString,不要先把十六进制字符串转成字节后再当作原文签名。编码文本的长度和解码后的长度是两件事。

Ed25519 验签诊断字段关系静态技术框图
传输编码先经过解码,再与长度常量和公钥来源建立诊断关系;摘要只用于定位,不替代原始字节。

建立可复查的验签诊断函数

不要把私钥、完整消息或完整签名写入日志。可以记录输入长度、编码方式、公钥来源标识和摘要。摘要只用来判断两端拿到的字节是否一致,不能把摘要当作新的验签输入。

// 诊断信息用于比对两端输入,不包含私钥和完整消息。
func digest8(data []byte) string {
    sum := sha256.Sum256(data)
    return hex.EncodeToString(sum[:8])
}

func diagnose(publicKey, message, signature []byte) (bool, map[string]any) {
    info := map[string]any{
        "message_len":    len(message),
        "message_sha8":   digest8(message),
        "public_len":     len(publicKey),
        "signature_len":  len(signature),
        "signature_sha8": digest8(signature),
    }
    if len(publicKey) != ed25519.PublicKeySize || len(signature) != ed25519.SignatureSize {
        return false, info // 长度失败先返回,避免触发公钥长度 panic。
    }
    ok := ed25519.Verify(ed25519.PublicKey(publicKey), message, signature)
    info["verified"] = ok
    return ok, info
}

这段代码还需要导入 crypto/sha256、encoding/hex 和 fmt(如果调用方要格式化日志)。示例中的字段名只是诊断约定,生产环境还应加上请求 ID、签名版本和公钥版本,便于发现“验签端拿到了旧公钥”的情况。

形成上线前的消息与公钥检查清单

  • 签名原文是否定义为固定 UTF-8 字节,是否禁止自动去空格或补换行。
  • 两端是否使用同一种 JSON 规范化方式,字段顺序和数字格式是否稳定。
  • 公钥是否来自正确的租户、版本或密钥 ID,长度是否等于 32 字节。
  • 签名字段是否先按协议解码,解码后长度是否等于 64 字节。
  • 诊断日志是否只记录长度、短摘要、编码方式和版本,不记录私钥与完整敏感消息。

如果消息摘要一致、公钥长度正确、签名长度正确但仍然失败,下一步应核对公钥是否与签名私钥配对,以及签名端是否使用了不同的 Ed25519 变体或协议域。不要在本篇边界内把普通 Verify 失败改成“换 Hash”或“先 SHA-512 一次”;那会改变被签名的消息语义。

相关问题

Ed25519 公钥为什么不是 64 字节?

标准库的 PublicKeySize 是 32 字节;64 字节通常是私钥表示的长度,不应把两者混用。

签名 Base64 解码后少于 64 字节怎么办?

先检查字段截断、编码方式和传输填充,修复解码链路后再验签;不要用补零把长度凑到 64 字节。

只比较消息长度能定位验签失败吗?

不能。相同长度的消息仍可能字节不同,应该同时比较短摘要,并回到原始消息的规范化约定。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>