go 1.20+ 首次内置 crypto/ecdh 包,低于该版本会报错“cannot find package”,必须升级 go;密钥交换需双方使用同曲线(如 p256),公钥须为压缩格式(p256 为 33 字节),ecdh 输出为原始共享密钥字节,需经 kdf 派生才可用于加密。

Go 1.20+ 才内置 crypto/ecdh,低于该版本无法直接使用——这是你跑不起来的首要原因。
确认 Go 版本并启用 ECDH 包
运行 go version 检查是否 ≥ 1.20。若为 1.19 或更低,crypto/ecdh 不存在,会报错 cannot find package "crypto/ecdh"。升级 Go 是唯一解,别试图用 golang.org/x/crypto/ecdh 替代——它已废弃且与标准库不兼容。
- Go 1.20+:直接导入
"crypto/ecdh" - 密钥交换必须配对使用同一条曲线,比如双方都用
ecdh.P256(),混用P256和P384会导致invalid public key - 标准库不支持自定义曲线参数;所有可用曲线由
ecdh.Curve类型预定义(P256、P384、P521)
生成私钥并导出公钥(Alice 端)
私钥生成是隐式的,调用 GenerateKey 即完成。注意:返回的 *ecdh.PrivateKey 不含公开可序列化的字段,必须显式调用 Public().Bytes() 获取压缩格式公钥字节(33 字节 for P256)。
curve := ecdh.P256()
priv, err := curve.GenerateKey(rand.Reader)
if err != nil {
log.Fatal(err)
}
pubBytes := priv.Public().Bytes() // 压缩格式,推荐传输/存储
-
Public().Bytes()返回压缩点(compressed point),不是原始 X/Y 坐标;解压需用curve.NewPublicKey(pubBytes) - 不要尝试用
priv.Bytes()—— 它返回的是内部编码,不可跨进程/语言使用 - 若需 JSON 序列化公钥,先 base64 编码
pubBytes,而非结构体直编
用对方公钥计算共享密钥(Bob 端)
Bob 拿到 Alice 的 pubBytes 后,必须用相同曲线重建公钥对象,再调用 Public().Bytes() 验证长度(P256 是 33 字节),否则 NewPublicKey 会静默失败或 panic。
curve := ecdh.P256()
bobPriv, _ := curve.GenerateKey(rand.Reader)
alicePub, err := curve.NewPublicKey(pubBytes)
if err != nil {
log.Fatal("bad pub key:", err) // 常见于字节长度错、非压缩格式、无效坐标
}
shared, err := bobPriv.ECDH(alicePub)
if err != nil {
log.Fatal(err) // 通常因 alicePub 验证失败
}
-
ECDH()输出是原始共享密钥字节(32 字节 for P256),**不是**密钥派生后的密钥;需配合crypto/hmac或crypto/rand+ KDF 使用 - 若传入未压缩公钥(65 字节),
NewPublicKey会返回invalid public key - 双方调换角色即可完成双向协商,无需额外握手逻辑
常见错误和边界情况
最易被忽略的是密钥用途混淆:crypto/ecdh 只做数学运算,不处理填充、认证、密钥派生或加密。直接拿 shared 当 AES 密钥用,大概率导致安全漏洞。
- 错误日志
invalid public key:90% 是公钥字节长度不对,或用了 OpenSSL 生成的未压缩格式 - 测试时用固定 seed(如
rand.New(rand.NewSource(0)))能复现密钥,但生产必须用crypto/rand.Reader - ECDSA 私钥不能当 ECDH 私钥用——二者虽同曲线,但结构和验证逻辑不同,强制转换会 panic
- P521 共享密钥长 66 字节,别硬写 32 字节 buffer
真正麻烦的从来不是算出共享密钥,而是后续怎么安全地把它变成能加密的密钥材料。别跳过 KDF 步骤,也别把 ECDH 当成“开箱即用的加密方案”。











