dsa在go 1.23中已被彻底移除,唯一受支持的替代方案是ecdsa(仅p-256/p-384),签名必须用signasn1生成der格式,pem类型标识须严格为"ec private key"/"public key",且需注意跨语言格式统一。

DSA 在 Go 里早就不该用了
Go 标准库的 crypto/dsa 包从 1.20 开始被标记为 deprecated,1.23 起彻底移除。不是“不推荐”,是直接删了——你升级 Go 版本后,import "crypto/dsa" 会编译失败。这不是兼容性问题,是标准库主动弃用。原因很明确:DSA 是 NIST 1991 年标准,密钥固定 1024 位(后来勉强支持 2048/3072),但 RFC 8692 明确指出 DSA 已不满足现代安全要求;OpenSSL、BoringSSL、Rust 的 ring 库等主流实现也都已弃用或限制使用。
替代方案只有 ECDSA,且必须选 P-256 或 P-384
Go 官方只保留并强化了 crypto/ecdsa,这是当前唯一被支持、有长期维护保障的非对称签名方案。P-256(即 elliptic.P256())是默认选择,适合绝大多数场景;P-384 更强,但性能略低,仅在合规要求明确指定时才用。别碰 P-521 —— Go 标准库不支持它生成私钥(ecdsa.GenerateKey 会 panic)。
常见错误现象:crypto/ecdsa: invalid elliptic curve,基本是因为传了不支持的曲线类型,或误用 crypto/elliptic.CurveParams 手动构造(不该这么干)。
- 生成密钥:用
ecdsa.GenerateKey(elliptic.P256(), rand.Reader),别自己拼big.Int - 签名:必须用
ecdsa.SignASN1(输出 ASN.1 编码的 DER 格式),不是原始 R+S 拼接 —— 否则其他语言(如 Python 的 cryptography)验不了 - 验签:对应用
ecdsa.VerifyASN1,输入的公钥必须是*ecdsa.PublicKey,不是interface{}或 PEM 解包后的泛型结构
PEM 编解码最容易漏掉的两个细节
很多人卡在“签名能生成,但别人验不过”,90% 是 PEM 封装格式不对。Go 的 crypto/x509 对 DSA/ECDSA 私钥的 PEM 类型标识有硬性要求,不能写错。
立即学习“go语言免费学习笔记(深入)”;
正确写法:
-----BEGIN EC PRIVATE KEY----- ...base64... -----END EC PRIVATE KEY-----
错误写法(常见于手动生成或 OpenSSL 转换):
-
-----BEGIN PRIVATE KEY-----(PKCS#8 通用格式)→ Go 的x509.ParseECPrivateKey不认这个,得用x509.ParsePKCS8PrivateKey,但返回的是interface{},还得类型断言 -
-----BEGIN DSA PRIVATE KEY-----→ Go 1.23+ 直接报错unknown PEM block type "DSA PRIVATE KEY"
公钥同理:-----BEGIN PUBLIC KEY----- 是 PKCS#8 公钥格式,Go 可以用 x509.ParsePKIXPublicKey 解析;而 -----BEGIN EC PUBLIC KEY----- 是旧格式,标准库不支持解析(会返回 unsupported key type)。
签名长度和确定性问题必须手动处理
ECDSA 签名本身是非确定性的(每次调用 SignASN1 结果不同),因为内部用了随机数。如果你需要可重现签名(比如做测试断言、或某些区块链场景),必须传入确定性随机源 —— 但 crypto/ecdsa 不提供接口。解决方案只有一个:crypto/rand.Read 替换为固定字节切片的伪随机读取器,例如:
type fixedReader struct{ b []byte }
func (r *fixedReader) Read(p []byte) (n int, err error) {
n = copy(p, r.b)
return n, nil
}
// 使用时:ecdsa.SignASN1(&fixedReader{b: bytes.Repeat([]byte{1}, 32)}, priv, hash.Sum(nil)[:], priv.Curve.Params().BitSize)注意:SignASN1 输出的签名长度固定(P-256 是 72 字节 DER 编码),但如果你看到 70 或 71 字节,说明 ASN.1 编码中整数前导零被省略了 —— 这是合法的,但某些旧系统可能要求严格补零,此时需手动解析 ASN.1 结构重编码。
真正麻烦的是跨语言互操作:Java 的 BouncyCastle 默认用 IEEE P1363 格式(R+S 拼接),而 Go 只输出 DER;Python 的 cryptography 库默认也期待 DER。两边不统一,验签必失败 —— 别指望“格式差不多就能通”,这里没灰色地带。










