Go 把密码学功能放在标准库中,从 Go 1.24 起,核心原语位于为 FIPS 140-3 构建的 Go 密码模块中。crypto/ecdsa 负责签名和验证,crypto/x509 读取密钥和证书;除 crypto/ed25519 外,Go 1.27.3 还加入了 crypto/mlkem(ML-KEM-768/1024)和 crypto/mldsa(ML-DSA-44/65/87)。标准库中没有 secp256k1:以太坊和比特币代码使用 go-ethereum 或 decred 的包。
crypto/ecdsa 中的所有操作都针对你计算好的摘要——这个包从不计算哈希。比生成错误的签名更糟的是,过长的输入会被静默截断到曲线长度:对 46 字节输入生成的签名,对另一个前 32 字节相同的输入也验证通过。务必像示例那样先计算哈希。SignASN1、VerifyASN1 和 crypto.Signer 接口使用 ASN.1 DER;WebCrypto、JWS 等使用原始 r‖s 的对端需要用 Sign/Verify,并用 FillBytes 填充整数——Bytes() 会丢掉前导零字节,因此凡是恰好以零字节开头的 r 或 s,输出都会少一个字节。
签名是随机化的,而且从 Go 1.26 起,除非设置了 GODEBUG=cryptocustomrand=1,否则你传入的 io.Reader 会被忽略:用全零 Reader 生成的两个签名并不相同。依赖固定 Reader 获得可复现签名的测试因此失效;文档给出的替代方案是 testing/cryptotest.SetGlobalRandom。s 没有经过规范化,VerifyASN1 对处于高半区和低半区的 s 都接受,因此面向严格要求 low-S 的验证方时,需要把签名中的 s 替换为 n − s。
密钥解析优先面向 DER。把 PEM 文本传给 x509.ParsePKIXPublicKey 会报错 asn1: structure error: tags don't match——应先调用 pem.Decode。ecdsa.ParseUncompressedPublicKey 和 ecdsa.ParseRawPrivateKey 读取裸 SEC1 点和标量;压缩点会被拒绝,也没有读取口令加密 PKCS#8 的函数。格式错误的签名会让 VerifyASN1 返回 false,而绝不会返回错误。
要将 Go 签名与其他技术栈比较,可以先在 DER ⇄ 原始格式转换器中转换,再在签名验证工具中检查;证书解析问题在证书解码器中最容易看出来。