crypto/dsa 不该在生产中使用,因其仅支持过时的 p-1024/q-160 和 p-2048/q-224 参数集,不兼容 fips 186-4,哈希长度强制匹配 q 位,签名需手动 der 编码,且无标准序列化方法。

Go 的 crypto/dsa 包早已被标记为 deprecated,不应用于新项目——它不支持 FIPS 186-4,密钥生成和签名逻辑存在已知局限(如仅支持固定参数集),且自 Go 1.22 起官方明确建议迁移到 crypto/ecdsa 或 crypto/ed25519。
为什么 crypto/dsa 不该在生产中使用
Go 标准库中的 DSA 实现只支持 NIST P-1024/Q-160 和 P-2048/Q-224 两组硬编码参数(对应 dsa.L1024N160 和 dsa.L2048N224),无法配置更安全的 P-3072/Q-256;签名时强制要求哈希输出长度严格等于 Q 位(即 160 或 224 位),导致你不能直接用 sha256.Sum256——必须截断或重哈希;更关键的是,dsa.Sign 要求传入的 []byte 必须是“已经按 Q 位截断的哈希值”,不是原始消息,这点极易出错。
- 调用
dsa.Sign(rand.Reader, priv, hash[:])时,若hash是sha256.Sum256的 32 字节结果,会 panic:"hash too large" -
dsa.GenerateKey返回的私钥不含序列化方法,无法直接用json.Marshal或 PEM 编码,需手动处理priv.X、priv.PublicKey.Y等字段 - 所有参数集都未通过 FIPS 验证,金融、政务等合规场景直接拒绝接受
如果必须兼容旧系统,怎么签一个能被验证的 DSA 签名
前提:你对接的是遗留 Java 或 OpenSSL 系统,且对方只认 DSA-SHA1(P-1024/Q-160)——这是唯一还能勉强跑通的组合。步骤必须严格对齐:
- 用
sha1.Sum160计算消息哈希,取全部 20 字节(hash[:] == 20,正好匹配 Q=160) - 调用
dsa.GenerateKey时指定dsa.L1024N160,不要用L2048N224(OpenSSL 默认不认) - 签名前确保
rand.Reader是加密安全的(不能用rand.New(&rand.StdSource{})) - 签名结果
r,s是两个大整数,需按 ASN.1 DER 编码才能被其他语言识别——标准库不提供,得手写或引入github.com/google/certificate-transparency-go中的encoding/asn1工具函数
示例关键片段:
hash := sha1.Sum160()
hash.Write([]byte("hello"))
r, s, err := dsa.Sign(rand.Reader, priv, hash[:])
// r,s 是 *big.Int,要转 DER 序列化才能给 Java 验证
替代方案:用 crypto/ecdsa 实现同等语义的签名
ECDSA 在 Go 中支持完整参数集(P-256/P-384/P-521)、原生 PEM 支持、标准 DER 签名输出,且性能更好。只需改三处:
- 密钥生成:用
ecdsa.GenerateKey(elliptic.P256(), rand.Reader)替代dsa.GenerateKey - 哈希:任意
hash.Hash(sha256.New()、sha512.New384())均可,无需截断 - 签名:直接
ecdsa.SignASN1(rand.Reader, priv, hash.Sum(nil), priv.Params().BitSize),返回的就是标准 DER 格式字节
验证端几乎无需修改——只要它支持 ECDSA(现代系统基本都支持),就能直接验这个签名。
真正麻烦的从来不是“怎么调用函数”,而是参数长度对齐、哈希预处理规则、以及 DER 编码格式是否被下游接受。哪怕你把 crypto/dsa 调通了,下个月对方系统升级到 FIPS 模式,签名就立刻失效。











