不推荐自行实现RSA密钥生成和序列化,因Go标准库rsa.GenerateKey已内置安全随机源与参数校验,裸写易导致私钥权限缺失(须os.OpenFile(..., 0600))、PEM格式错误、1024位密钥降级等高危漏洞。

直接用 crypto/rsa 或 crypto/ecdsa 封装非对称加密模块,不推荐裸写密钥管理、填充逻辑或错误处理——90% 的安全漏洞来自封装层的疏漏,而非算法本身。
为什么别自己实现 RSA 密钥生成和序列化
Go 标准库的 rsa.GenerateKey 已内置安全随机源和参数校验,但很多人会跳过 rand.Reader 可用性检查,或误用 big.Int 手动构造密钥。更常见的是把私钥以明文 PEM 写入文件却没设文件权限(0600),导致 cat data/rsa_private_key.pem 就能泄露全部密钥。
- 必须用
os.OpenFile(..., 0600)写私钥文件,Linux/macOS 下chmod 600不是补救手段,是封装时就该强制的约束 - 公钥导出不要用
x509.MarshalPKIXPublicKey后再手动拼 PEM 头尾,应直接用pem.Encode,否则易出格式错(如漏换行、多空格) - 密钥长度选
2048或3072;1024在 Go 1.20+ 已被部分 TLS 库拒绝,且crypto/rsa的SignPKCS1v15对1024会静默降级
如何安全封装 Encrypt/Decrypt 接口
crypto/rsa 的 EncryptOAEP 和 DecryptOAEP 是目前最稳妥的选择,但封装时容易忽略盐值(label)和哈希函数一致性。比如用 sha256.New() 加密,解密时却传 sha512.New(),会直接返回 "crypto/rsa: decryption error" 而不是具体原因。
- 加密函数签名建议为
func Encrypt(pub *rsa.PublicKey, plaintext []byte) ([]byte, error),内部固定用sha256和空label;业务需自定义 label 时,应显式暴露参数,不默认留空 - 解密前必须校验密文长度:OAEP 密文长度 = 公钥模长(
pub.N.BitLen() / 8),少一字节就直接return nil, errors.New("invalid ciphertext length"),避免 padding oracle 类型的侧信道试探 - 永远不要在封装层做
base64.StdEncoding.EncodeToString—— 编码应由调用方决定;你的模块只负责[]byte → []byte
ECDSA 封装比 RSA 更容易踩坑的地方
crypto/ecdsa 不提供直接加解密(它本质是签名算法),但常有人误用 Sign 当加密、Verify 当解密。真要加密,得组合 crypto/ecdh(Go 1.20+)做密钥协商,再套 AES,整个流程不能省略 KDF(密钥派生)步骤。
立即学习“go语言免费学习笔记(深入)”;
- 若封装 ECDH + AES-GCM,必须用
hkdf.New(sha256.New, sharedKey, salt, info)派生出两个密钥:一个给 AES-GCM 的cipher.NewGCM,一个给 GCM 的 nonce 生成器(不能复用) -
ecdh.PrivateKey.Ephemeral必须每次加密都新建,绝不可复用;否则攻击者截获两次密文就能恢复共享密钥 - ECDSA 签名封装中,
rand.Reader不可用时不能 fallback 到math/rand,必须 panic 或返回明确错误 —— 伪随机会导致私钥泄露(参考 Android Java SecureRandom 漏洞)
真正难的不是调用 rsa.EncryptOAEP,而是让模块在密钥加载失败、磁盘满、rand.Reader 阻塞、甚至 panic 时仍不泄露中间态数据。所有临时 []byte 都要用 crypto/subtle.ConstantTimeCompare 清零,而不是简单 make([]byte, 0) —— 这点几乎没人检查,但内存 dump 时就是突破口。


















