使用crypto/rsa签名验签需先解析PEM私钥为*rsa.PrivateKey,签名前用指定hash.Hash计算摘要,验签时确保公钥类型断言正确、哈希一致、数据标准化,优先选rsa.SignPSS并配好PSSOptions。

用 crypto/rsa 做签名和验签,私钥必须是 PEM 解码后的 *rsa.PrivateKey
Go 标准库不直接接受 PEM 字符串做签名,你得先解析。常见错误是把 PEM 内容传给 rsa.SignPKCS1v15 就报错:crypto: requested hash function is not available——这往往是因为没传正确的 hash.Hash 实例,或私钥没正确解码。
实操要点:
- 私钥文件(如
private.pem)需以-----BEGIN RSA PRIVATE KEY-----或-----BEGIN PRIVATE KEY-----开头;前者用pem.Decode后调x509.ParsePKCS1PrivateKey,后者用x509.ParsePKCS8PrivateKey - 签名前必须显式计算哈希:先用
sha256.New()拿到hash.Hash,写入原始数据,再传hash.Sum(nil)的结果给rsa.SignPKCS1v15 - 别跳过错误检查:比如
rsa.VerifyPKCS1v15返回crypto.ErrVerification时,不等于“验签失败”,也可能是哈希对象没重置、公钥格式不对、或签名长度不匹配密钥位数
rsa.SignPKCS1v15 和 rsa.SignPSS 怎么选
PKCS#1 v1.5 是兼容性最好的方案,但 PSS 更安全(概率性填充,抗选择明文攻击)。实际项目里,除非对接老系统强制要求 v1.5,否则优先用 rsa.SignPSS。
关键区别:
立即学习“go语言免费学习笔记(深入)”;
-
SignPSS必须传*rsa.PSSOptions,其中SaltLength推荐设为rsa.PSSSaltLengthAuto(让 Go 自动按哈希长度推算),别硬写0或-1,否则验签必失败 - PSS 验签时,
opts中的Hash字段必须和签名时用的哈希完全一致,且crypto.Hash值要对得上(比如crypto.SHA256,不是随便一个sha256.New()) - v1.5 签名输出长度固定 = 密钥字节数(如 2048 位 → 256 字节),PSS 输出长度相同,但内部填充随机,同一消息多次签名结果不同
公钥从 *rsa.PublicKey 构造时,别漏掉 crypto/rsa 的类型断言
如果你从证书(*x509.Certificate)里取公钥,它类型是 interface{},直接传给 rsa.VerifyPKCS1v15 会 panic:interface conversion: interface {} is *rsa.PublicKey, not *rsa.PublicKey——看着矛盾?其实是包路径不一致导致的类型不等价。
安全做法:
- 从证书取:用
cert.PublicKey.(*rsa.PublicKey)显式断言(确保 cert.SignatureAlgorithm 是 RSA 类型) - 从 PEM 解析公钥:用
pem.Decode+x509.ParsePKIXPublicKey,返回的是interface{},同样要断言为*rsa.PublicKey - 如果断言失败,说明公钥不是 RSA 类型(比如是 ECDSA),得换对应验签函数,不能硬转
验签失败但错误信息是 crypto.ErrVerification,先查三件事
这个错误最让人懵,它不告诉你哪错了。实际排查顺序建议倒过来:从输入源头开始卡点。
- 确认签名字节长度是否等于 RSA 密钥模长(如 2048 位密钥 → 签名必须是 256 字节;少一字节就直接失败)
- 确认验签用的哈希算法和签名时完全一致,且哈希对象在写入数据前已通过
hash.Reset()清空(尤其复用hash.Hash实例时) - 确认原始数据(未哈希前)在签名和验签两端完全一致:空格、换行、BOM、编码(UTF-8 还是 GBK)——哪怕多一个 \r 都会导致哈希值不同
最常被忽略的是数据标准化:HTTP body、JSON 字符串、XML 文本,在签名前必须做确定性序列化(比如 JSON 要禁用 MarshalIndent,排序 key,去掉空格),否则前端和后端算出的哈希天然不一致。


















