Files
encryptx/rsax/rsax.go
T

683 lines
28 KiB
Go
Raw Normal View History

2026-09-30 15:49:12 +08:00
// Package rsax 提供 RSA 密钥生成、PEM 编解码、加解密与数字签名的安全封装。
//
// 设计约定:
// - 任何失败路径都返回 error,绝不 panic;
// - 加解密默认使用 OAEP + SHA-256,签名默认使用 PSS + SHA-256;
// - 二进制数据(密钥、明文、密文、签名)一律使用 []byte;
// - 不把私钥、明文或随机数写入日志、标准输出或 error 文本。
//
// 安全注意事项:
// - 密钥长度下限为 MinKeyBits(2048 位),更短的密钥会被拒绝;
// - PKCS#1 v1.5 加密(EncryptPKCS1v15)仅为兼容老旧系统保留,新代码应使用 OAEP;
// - EncryptOAEPLong 的分块格式自身不携带完整性校验与边界信息,
// 在需要抵御密文篡改或重排的场景,应先对明文做 AEAD/签名保护,或只使用单块 EncryptOAEP。
2023-12-27 19:00:14 +08:00
package rsax
import (
2026-09-30 15:49:12 +08:00
"crypto"
2023-12-27 19:00:14 +08:00
"crypto/rand"
"crypto/rsa"
2026-09-30 15:49:12 +08:00
"crypto/sha256"
2023-12-27 19:00:14 +08:00
"crypto/x509"
"encoding/pem"
2026-09-30 15:49:12 +08:00
"errors"
"fmt"
"hash"
2023-12-27 19:00:14 +08:00
"os"
)
2026-09-30 15:49:12 +08:00
// cryptoHash 是 OAEP 与签名使用的标准库哈希标识,必须与 rsaHash 返回的构造器一致。
// 安全注意事项:修改该值会同时改变 OAEP 填充与签名格式,导致与既有数据不兼容。
const cryptoHash = crypto.SHA256
2023-12-27 19:00:14 +08:00
2026-09-30 15:49:12 +08:00
// 密钥长度等关键参数集中定义;调用方应直接引用这些常量,不要硬编码数字。
const (
// MinKeyBits 是允许**生成**的最小 RSA 密钥长度(位)。
// 安全注意事项:小于 2048 位的 RSA 密钥已不满足现代安全强度,
// 所有生成入口都会拒绝更小的值。
MinKeyBits = 2048
// MinUsableKeyBits 是允许**解析和使用既有密钥**的最小长度(位)。
//
// 它低于 MinKeyBits 是刻意的:旧系统里真实存在 1024 位密钥,若连解析都
// 拒绝,持有这类密钥的调用方将无法解密自己既有的数据——那是把安全策略
// 变成了功能性故障。因此本库只在**生成**环节强制 2048 位,对既有密钥允许
// 使用到该下限,并通过 IsWeakPrivateKey / IsWeakPublicKey 让调用方自行判断
// 是否要告警或拒绝。
//
// 低于 1024 位的 RSA 已可被低成本分解,无论何种理由都不接受。
// 该值与 Go 标准库自身的下限一致(标准库已拒绝生成低于 1024 位的密钥)。
MinUsableKeyBits = 1024
// WeakKeyBits 是"弱密钥"的判定阈值(位):低于该值即视为不满足当前建议强度。
// IsWeakPrivateKey / IsWeakPublicKey 以此为判断依据。
WeakKeyBits = 2048
// DefaultKeyBits 是未显式指定长度时使用的默认 RSA 密钥长度(位)。
// 安全注意事项:2048 位是当前通用场景的最低推荐值,
// 需要长期保密或高价值数据时请显式指定更大长度。
DefaultKeyBits = 2048
// MaxKeyBits 是允许生成的最大 RSA 密钥长度(位)。
// 安全注意事项:设置上限用于防止误传超大位宽导致生成过程长时间占用 CPU 与内存。
MaxKeyBits = 8192
// OAEPHash 是 OAEP 加解密与签名默认使用的哈希算法名。
// 安全注意事项:SHA-256 与 2048 位密钥组合是当前推荐配置;
// 若生成 8192 位等超大密钥并需要更高强度,可另行封装 SHA-512 版本。
OAEPHash = "SHA-256"
)
// IsWeakPrivateKey 报告私钥是否弱于当前建议强度(WeakKeyBits)。
//
// 用于在解析既有密钥后决定是否告警或强制轮换;nil 或参数缺失一律视为弱密钥。
func IsWeakPrivateKey(priv *rsa.PrivateKey) bool {
if priv == nil || priv.N == nil {
return true
2023-12-27 19:00:14 +08:00
}
2026-09-30 15:49:12 +08:00
return priv.N.BitLen() < WeakKeyBits
2023-12-27 19:00:14 +08:00
}
2026-09-30 15:49:12 +08:00
// IsWeakPublicKey 报告公钥是否弱于当前建议强度(WeakKeyBits)。
func IsWeakPublicKey(pub *rsa.PublicKey) bool {
if pub == nil || pub.N == nil {
return true
2023-12-27 19:00:14 +08:00
}
2026-09-30 15:49:12 +08:00
return pub.N.BitLen() < WeakKeyBits
2023-12-27 19:00:14 +08:00
}
2026-09-30 15:49:12 +08:00
const (
// pemTypePublicKey 是 PKIX(SubjectPublicKeyInfo)公钥的 PEM 标签。
pemTypePublicKey = "PUBLIC KEY"
// pemTypePKCS1PublicKey 是 PKCS#1 公钥的 PEM 标签。
pemTypePKCS1PublicKey = "RSA PUBLIC KEY"
// pemTypePrivateKey 是 PKCS#8 私钥的 PEM 标签。
pemTypePrivateKey = "PRIVATE KEY"
// pemTypePKCS1PrivateKey 是 PKCS#1 私钥的 PEM 标签。
pemTypePKCS1PrivateKey = "RSA PRIVATE KEY"
)
// 包级哨兵错误,调用方可用 errors.Is 判断失败类别,无需解析错误文本。
var (
// ErrInvalidKeySize 表示 RSA 密钥长度不在允许范围内。
ErrInvalidKeySize = errors.New("rsax: invalid RSA key size")
// ErrNilKey 表示传入了 nil 密钥或缺少必要参数。
ErrNilKey = errors.New("rsax: nil RSA key")
// ErrInvalidPEM 表示 PEM 输入为空、无法解码或标签不受支持。
ErrInvalidPEM = errors.New("rsax: invalid PEM data")
// ErrInvalidCiphertext 表示密文为空、长度不合法或无法解密。
ErrInvalidCiphertext = errors.New("rsax: invalid ciphertext")
// ErrInvalidSignature 表示签名验证失败。
ErrInvalidSignature = errors.New("rsax: signature verification failed")
// ErrEmptyPath 表示文件路径为空。
ErrEmptyPath = errors.New("rsax: empty file path")
)
// 私钥文件、公钥文件的权限位(POSIX 语义)。
const (
privateKeyFileMode os.FileMode = 0o600
publicKeyFileMode os.FileMode = 0o644
)
// rsaHash 返回加解密与签名使用的哈希构造器。
// 安全注意事项:返回值必须与 OAEP 的单块长度计算保持一致,
// 修改此处会改变 padding 结构,导致新旧版本密文/签名互不兼容。
func rsaHash() hash.Hash { return sha256.New() }
// hashSize 是默认哈希(SHA-256)的输出长度(字节)。
func hashSize() int { return sha256.Size }
// checkKeySize 校验请求的密钥长度并补充空值情况说明。
//
// bits 为期望的密钥长度(位)。安全注意事项:只有 [MinKeyBits, MaxKeyBits]
// 区间内的长度才被接受,拒绝弱密钥与可能耗尽资源的超大位宽。
func checkKeySize(bits int) error {
if bits < MinKeyBits {
return fmt.Errorf("%w: need at least %d bits, got %d: 弱密钥会被拒绝", ErrInvalidKeySize, MinKeyBits, bits)
2023-12-27 19:00:14 +08:00
}
2026-09-30 15:49:12 +08:00
if bits > MaxKeyBits {
return fmt.Errorf("%w: need at most %d bits, got %d", ErrInvalidKeySize, MaxKeyBits, bits)
2023-12-27 19:00:14 +08:00
}
2026-09-30 15:49:12 +08:00
return nil
}
// checkPrivateKey 校验私钥非 nil 且长度不低于可使用的下限。
//
// priv 为待校验的私钥。这里用的是 MinUsableKeyBits 而非 MinKeyBits:本函数服务
// 于"使用既有密钥"的路径(解析、加解密、签名),允许 2048 位以下的旧密钥继续
// 工作,只拒绝低于绝对下限的密钥。是否对弱密钥告警请用 IsWeakPrivateKey。
func checkPrivateKey(priv *rsa.PrivateKey) error {
if priv == nil {
return fmt.Errorf("%w: private key", ErrNilKey)
}
if priv.N == nil || priv.D == nil {
return fmt.Errorf("%w: private key 缺少 N/D 参数", ErrNilKey)
}
if bits := priv.N.BitLen(); bits < MinUsableKeyBits {
return fmt.Errorf("%w: private key 只有 %d 位, 少于可使用下限 %d 位",
ErrInvalidKeySize, bits, MinUsableKeyBits)
}
return nil
}
// checkPublicKey 校验公钥非 nil 且长度不低于可使用的下限。
//
// pub 为待校验的公钥。与 checkPrivateKey 同理,这里允许既有弱公钥继续使用,
// 只拒绝低于 MinUsableKeyBits 的密钥。
func checkPublicKey(pub *rsa.PublicKey) error {
if pub == nil {
return fmt.Errorf("%w: public key", ErrNilKey)
}
if pub.N == nil {
return fmt.Errorf("%w: public key 缺少 N 参数", ErrNilKey)
}
if bits := pub.N.BitLen(); bits < MinUsableKeyBits {
return fmt.Errorf("%w: public key 只有 %d 位, 少于可使用下限 %d 位",
ErrInvalidKeySize, bits, MinUsableKeyBits)
}
return nil
}
// GenerateKey 生成一对 RSA 密钥,返回私钥(公钥可通过 priv.PublicKey 获取)。
//
// bits 为密钥长度(位),必须位于 [MinKeyBits, MaxKeyBits] 区间内;
// 传入 0 之外的越界值都会返回包装了错误原因的 error。
// 安全注意事项:随机源固定为 crypto/rand,密钥生成失败时不会返回半成品密钥。
func GenerateKey(bits int) (*rsa.PrivateKey, error) {
if err := checkKeySize(bits); err != nil {
return nil, err
}
priv, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, fmt.Errorf("rsax: 生成 %d 位 RSA 密钥失败: %w", bits, err)
}
if err := priv.Validate(); err != nil {
return nil, fmt.Errorf("rsax: 生成的 RSA 私钥未通过自检: %w", err)
}
return priv, nil
}
// GenerateKeyPairPEM 生成一对 RSA 密钥并直接返回 PEM 编码结果。
//
// bits 为密钥长度(位),取值范围同 GenerateKey。私钥使用 PKCS#8 编码,
// 公钥使用 PKIX 编码。返回的切片仅在内存中流转,调用方负责在不再需要时清零。
// 安全注意事项:私钥 PEM 属于高度敏感数据,不要写入日志或通过网络明文传输。
func GenerateKeyPairPEM(bits int) (privatePEM, publicPEM []byte, err error) {
priv, err := GenerateKey(bits)
if err != nil {
return nil, nil, err
}
privatePEM, err = MarshalPrivateKeyPEM(priv)
if err != nil {
return nil, nil, err
}
publicPEM, err = MarshalPublicKeyPEM(&priv.PublicKey)
if err != nil {
return nil, nil, err
}
return privatePEM, publicPEM, nil
}
// MarshalPrivateKeyPEM 把私钥编码为 PKCS#8 的 PEM 数据。
//
// priv 为待编码私钥,必须非 nil 且长度不小于 MinKeyBits。
// 安全注意事项:返回内容包含私钥明文材料,等价于私钥本身,须妥善保管。
func MarshalPrivateKeyPEM(priv *rsa.PrivateKey) ([]byte, error) {
if err := checkPrivateKey(priv); err != nil {
return nil, err
}
der, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("rsax: 编码 PKCS#8 私钥失败: %w", err)
}
block := &pem.Block{Type: pemTypePrivateKey, Bytes: der}
out := pem.EncodeToMemory(block)
if out == nil {
return nil, fmt.Errorf("rsax: 生成私钥 PEM 失败: %w", ErrInvalidPEM)
}
return out, nil
}
// MarshalPublicKeyPEM 把公钥编码为 PKIX(SubjectPublicKeyInfo)的 PEM 数据。
//
// pub 为待编码公钥,必须非 nil 且长度不小于 MinKeyBits。
// 安全注意事项:公钥可公开分发,但仍应确认其来源真实,避免被替换为攻击者公钥。
func MarshalPublicKeyPEM(pub *rsa.PublicKey) ([]byte, error) {
if err := checkPublicKey(pub); err != nil {
return nil, err
}
der, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, fmt.Errorf("rsax: 编码 PKIX 公钥失败: %w", err)
}
block := &pem.Block{Type: pemTypePublicKey, Bytes: der}
out := pem.EncodeToMemory(block)
if out == nil {
return nil, fmt.Errorf("rsax: 生成公钥 PEM 失败: %w", ErrInvalidPEM)
}
return out, nil
}
// ParsePrivateKeyPEM 从 PEM 数据解析 RSA 私钥,同时支持 PKCS#1 与 PKCS#8。
//
// data 为 PEM 文本(通常来自文件或配置),不得为空。
// 解析失败(空输入、非 PEM、DER 损坏、不是 RSA 私钥、密钥长度不足)一律返回 error。
// 安全注意事项:不支持的算法(如 ECDSA/Ed25519 私钥)会被明确拒绝,绝不降级或猜测;
// 调用方应把解析失败的 PEM 视为不可信输入,不要回显其内容。
func ParsePrivateKeyPEM(data []byte) (*rsa.PrivateKey, error) {
if len(data) == 0 {
return nil, fmt.Errorf("%w: 输入为空", ErrInvalidPEM)
}
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("%w: 无法解码 PEM(缺少 BEGIN/END 边界或格式非法)", ErrInvalidPEM)
}
if len(block.Bytes) == 0 {
return nil, fmt.Errorf("%w: PEM 块内容为空", ErrInvalidPEM)
}
var (
priv *rsa.PrivateKey
ok bool
err error
)
switch block.Type {
case pemTypePKCS1PrivateKey:
priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
case pemTypePrivateKey:
var key any
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
if err == nil {
priv, ok = key.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("%w: PKCS#8 私钥不是 RSA 类型(%T)", ErrInvalidPEM, key)
}
}
default:
return nil, fmt.Errorf("%w: 不支持的私钥 PEM 标签 %q", ErrInvalidPEM, block.Type)
}
if err != nil {
return nil, fmt.Errorf("rsax: 解析私钥失败: %w", err)
}
if err := checkPrivateKey(priv); err != nil {
return nil, err
}
if err := priv.Validate(); err != nil {
return nil, fmt.Errorf("rsax: 私钥参数不合法: %w", err)
}
return priv, nil
}
// ParsePublicKeyPEM 从 PEM 数据解析 RSA 公钥,同时支持 PKIX 与 PKCS#1。
//
// data 为 PEM 文本(通常来自文件或配置),不得为空。
// 解析失败(空输入、非 PEM、DER 损坏、不是 RSA 公钥、密钥长度不足)一律返回 error。
// 安全注意事项:非 RSA 公钥会被拒绝;调用方应通过可信渠道获取公钥文本,
// 否则可能被中间人替换导致加密内容泄露。
func ParsePublicKeyPEM(data []byte) (*rsa.PublicKey, error) {
if len(data) == 0 {
return nil, fmt.Errorf("%w: 输入为空", ErrInvalidPEM)
}
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("%w: 无法解码 PEM(缺少 BEGIN/END 边界或格式非法)", ErrInvalidPEM)
}
if len(block.Bytes) == 0 {
return nil, fmt.Errorf("%w: PEM 块内容为空", ErrInvalidPEM)
}
var (
pub *rsa.PublicKey
ok bool
err error
)
switch block.Type {
case pemTypePublicKey:
var key any
key, err = x509.ParsePKIXPublicKey(block.Bytes)
if err == nil {
pub, ok = key.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("%w: PKIX 公钥不是 RSA 类型(%T)", ErrInvalidPEM, key)
}
}
case pemTypePKCS1PublicKey:
pub, err = x509.ParsePKCS1PublicKey(block.Bytes)
default:
return nil, fmt.Errorf("%w: 不支持的公钥 PEM 标签 %q", ErrInvalidPEM, block.Type)
}
if err != nil {
return nil, fmt.Errorf("rsax: 解析公钥失败: %w", err)
}
if err := checkPublicKey(pub); err != nil {
return nil, err
}
return pub, nil
}
// LoadPrivateKey 从文件读取并解析 RSA 私钥(PKCS#1 或 PKCS#8)。
//
// path 为私钥 PEM 文件路径,不得为空;读取失败或内容不合法都返回 error。
// 安全注意事项:调用方应保证该文件权限为 0600 且不被其他用户或进程读写。
func LoadPrivateKey(path string) (*rsa.PrivateKey, error) {
if path == "" {
return nil, fmt.Errorf("%w: 私钥路径为空", ErrEmptyPath)
}
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("rsax: 读取私钥文件失败: %w", err)
}
defer clear(data)
priv, err := ParsePrivateKeyPEM(data)
if err != nil {
return nil, fmt.Errorf("rsax: 解析私钥文件 %s 失败: %w", path, err)
}
return priv, nil
}
// LoadPublicKey 从文件读取并解析 RSA 公钥(PKIX 或 PKCS#1)。
//
// path 为公钥 PEM 文件路径,不得为空;读取失败或内容不合法都返回 error。
// 安全注意事项:公钥文件可能被替换,加载后应结合证书链或指纹校验其真实性。
func LoadPublicKey(path string) (*rsa.PublicKey, error) {
if path == "" {
return nil, fmt.Errorf("%w: 公钥路径为空", ErrEmptyPath)
}
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("rsax: 读取公钥文件失败: %w", err)
}
pub, err := ParsePublicKeyPEM(data)
if err != nil {
return nil, fmt.Errorf("rsax: 解析公钥文件 %s 失败: %w", path, err)
}
return pub, nil
}
// SaveKeyPair 生成一对 RSA 密钥并写入指定的 PEM 文件。
//
// privatePath、publicPath 分别为私钥、公钥输出路径,均不得为空,且不能相同;
// bits 取值范围同 GenerateKey。私钥文件权限 0600,公钥文件权限 0644。
// 安全注意事项:若私钥路径已存在会被覆盖,请先在应用层确认不会误删既有密钥;
// 在 Windows 上 0600/0644 仅作尽力而为的权限设置,实际访问控制依赖 ACL。
func SaveKeyPair(privatePath, publicPath string, bits int) error {
if privatePath == "" || publicPath == "" {
return fmt.Errorf("%w: 私钥或公钥路径为空", ErrEmptyPath)
}
if privatePath == publicPath {
return fmt.Errorf("%w: 私钥与公钥路径相同 %s", ErrEmptyPath, privatePath)
}
privatePEM, publicPEM, err := GenerateKeyPairPEM(bits)
if err != nil {
return err
}
defer clear(privatePEM)
if err := os.WriteFile(privatePath, privatePEM, privateKeyFileMode); err != nil {
return fmt.Errorf("rsax: 写入私钥文件 %s 失败: %w", privatePath, err)
}
if err := os.WriteFile(publicPath, publicPEM, publicKeyFileMode); err != nil {
return fmt.Errorf("rsax: 写入公钥文件 %s 失败: %w", publicPath, err)
}
return nil
}
// maxOAEPMessageLen 返回单块 OAEP 加密可容纳的最大明文长度。
//
// k 为 RSA 模长(字节);哈希长度由 hashSize 决定。
// 计算依据 PKCS#1 v2.2:k - 2*hLen - 2。
func maxOAEPMessageLen(k int) int {
return k - 2*hashSize() - 2
}
// EncryptOAEP 使用 RSA-OAEP(SHA-256) 加密单块明文。
//
// pub 为接收方公钥,plaintext 为明文,长度不得超过 len(pub)-2*32-2 字节。
// 明文过长时返回明确错误,此时可改用 EncryptOAEPLong。
// 安全注意事项:OAEP 只保证机密性,不提供完整性;密文被篡改后解密会失败,
// 但攻击者仍可观察失败与否,需要抵抗选择密文攻击时请配合 AEAD 或签名使用。
func EncryptOAEP(pub *rsa.PublicKey, plaintext []byte) ([]byte, error) {
if err := checkPublicKey(pub); err != nil {
return nil, err
}
maxLen := maxOAEPMessageLen(pub.Size())
if len(plaintext) > maxLen {
return nil, fmt.Errorf("rsax: 明文长度 %d 超过单块 OAEP 上限 %d 字节, 请改用 EncryptOAEPLong", len(plaintext), maxLen)
}
ciphertext, err := rsa.EncryptOAEP(rsaHash(), rand.Reader, pub, plaintext, nil)
if err != nil {
return nil, fmt.Errorf("rsax: OAEP 加密失败: %w", err)
}
return ciphertext, nil
}
// DecryptOAEP 解密单块 RSA-OAEP(SHA-256) 密文。
//
// priv 为接收方私钥,必须在长度上不小于 MinKeyBits;
// ciphertext 必须是恰好 len(priv)-字节的单块密文,否则返回错误。
// 安全注意事项:解密失败的具体原因(长度错误、padding 非法)不会泄露明文信息,
// 但调用方不应把底层错误细节返回给远端攻击者。
func DecryptOAEP(priv *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
if err := checkPrivateKey(priv); err != nil {
return nil, err
}
if len(ciphertext) == 0 {
return nil, fmt.Errorf("%w: 密文为空", ErrInvalidCiphertext)
}
if k := priv.Size(); len(ciphertext) != k {
return nil, fmt.Errorf("%w: 密文长度 %d 不是密钥长度 %d", ErrInvalidCiphertext, len(ciphertext), k)
}
plaintext, err := rsa.DecryptOAEP(rsaHash(), rand.Reader, priv, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("rsax: OAEP 解密失败: %w", err)
}
return plaintext, nil
}
// EncryptOAEPLong 使用 RSA-OAEP(SHA-256) 分块加密任意长度的明文。
//
// pub 为接收方公钥,plaintext 为明文,可为空(返回空密文与 nil);
// 每块明文长度上限为 k-2*hLen-2(k = pub.Size(),hLen = SHA-256 输出长度),
// 逐块调用 EncryptOAEP 后顺序拼接,密文总长度为 k 的整数倍。
// 安全注意事项:分块格式不含块边界以外的元数据,也不提供跨块完整性,
// 攻击者可能重排或删除整块密文而不被检测;如需抗篡改请先做 AEAD/签名,
// 或仅对不超过单块上限的明文使用 EncryptOAEP。
func EncryptOAEPLong(pub *rsa.PublicKey, plaintext []byte) ([]byte, error) {
if err := checkPublicKey(pub); err != nil {
return nil, err
}
if len(plaintext) == 0 {
return []byte{}, nil
}
k := pub.Size()
blockSize := maxOAEPMessageLen(k)
if blockSize <= 0 {
return nil, fmt.Errorf("rsax: 密钥长度 %d 字节过小, 无法进行 OAEP 分块", k)
}
blockCount := (len(plaintext) + blockSize - 1) / blockSize
ciphertext := make([]byte, 0, blockCount*k)
for offset := 0; offset < len(plaintext); offset += blockSize {
end := offset + blockSize
if end > len(plaintext) {
end = len(plaintext)
}
chunk, err := EncryptOAEP(pub, plaintext[offset:end])
if err != nil {
return nil, fmt.Errorf("rsax: 分块加密第 %d 块失败: %w", offset/blockSize, err)
}
ciphertext = append(ciphertext, chunk...)
}
return ciphertext, nil
}
// DecryptOAEPLong 解密由 EncryptOAEPLong 产生的分块密文。
//
// priv 为接收方私钥,ciphertext 必须是 k 的整数倍(k = priv.Size())且非空,
// 否则返回错误;每块独立解密后按顺序拼接还原完整明文。
// 安全注意事项:本函数不做跨块完整性校验,无法发现整块的删除、重排或替换;
// 若对端使用不同公钥加密相同明文,可能出现块替换攻击,因此请配合上层完整性保护。
func DecryptOAEPLong(priv *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
if err := checkPrivateKey(priv); err != nil {
return nil, err
}
if len(ciphertext) == 0 {
return nil, fmt.Errorf("%w: 密文为空", ErrInvalidCiphertext)
}
k := priv.Size()
if len(ciphertext)%k != 0 {
return nil, fmt.Errorf("%w: 密文长度 %d 不是密钥长度 %d 的整数倍", ErrInvalidCiphertext, len(ciphertext), k)
}
blockSize := maxOAEPMessageLen(k)
blockCount := len(ciphertext) / k
plaintext := make([]byte, 0, blockCount*blockSize)
for offset := 0; offset < len(ciphertext); offset += k {
chunk, err := DecryptOAEP(priv, ciphertext[offset:offset+k])
if err != nil {
return nil, fmt.Errorf("rsax: 分块解密第 %d 块失败: %w", offset/k, err)
}
plaintext = append(plaintext, chunk...)
}
return plaintext, nil
}
// EncryptPKCS1v15 使用 RSAES-PKCS1-v1_5 加密单块明文。
//
// pub 为接收方公钥,plaintext 长度不得超过 len(pub)-11 字节。
// 安全注意事项:该方案是为兼容老旧系统保留的,存在 Bleichenbacher 类填充预言机风险,
// 新代码必须优先使用 EncryptOAEP/EncryptOAEPLong;
// 若对端只能识别 PKCS#1 v1.5,请确保解密失败信息不会暴露给攻击者。
func EncryptPKCS1v15(pub *rsa.PublicKey, plaintext []byte) ([]byte, error) {
if err := checkPublicKey(pub); err != nil {
return nil, err
}
//lint:ignore SA1019 PKCS#1 v1.5 加密自 Go 1.26 起被标准库标记为危险;本函数仅为解密既有密文/与旧系统互操作而提供,函数注释已明确建议改用 OAEP。
ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, pub, plaintext)
if err != nil {
return nil, fmt.Errorf("rsax: PKCS#1 v1.5 加密失败: %w", err)
}
return ciphertext, nil
}
// DecryptPKCS1v15 解密单块 RSAES-PKCS1-v1_5 密文。
//
// priv 为接收方私钥,ciphertext 必须恰好为 len(priv) 字节。
// 安全注意事项:解密错误(包含填充校验失败)会被完整包装返回,
// 调用方绝不能把该错误直接回显给远端,否则会形成填充预言机导致明文泄露;
// 新代码请使用 DecryptOAEP。
func DecryptPKCS1v15(priv *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
if err := checkPrivateKey(priv); err != nil {
return nil, err
}
if len(ciphertext) == 0 {
return nil, fmt.Errorf("%w: 密文为空", ErrInvalidCiphertext)
}
if k := priv.Size(); len(ciphertext) != k {
return nil, fmt.Errorf("%w: 密文长度 %d 不是密钥长度 %d", ErrInvalidCiphertext, len(ciphertext), k)
}
//lint:ignore SA1019 PKCS#1 v1.5 解密自 Go 1.26 起被标准库标记为危险(错误信息会泄漏信息、可能构成 padding oracle);本函数仅为解密既有密文而提供,函数注释已明确建议改用 OAEP。
plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
if err != nil {
return nil, fmt.Errorf("rsax: PKCS#1 v1.5 解密失败: %w", err)
}
return plaintext, nil
}
// SignPSS 使用 RSASSA-PSS(SHA-256) 对消息签名。
//
// priv 为签名私钥,message 为待签名消息原文(函数内部完成哈希)。
// 盐长度固定为 rsa.PSSSaltLengthEqualsHash(等于哈希输出长度 32 字节),
// 这样签名结果长度确定,且符合 RFC 8017 推荐做法。
// 安全注意事项:签名私钥泄露等同于身份泄露,请确保私钥以 0600 权限保存;
// 本函数对 message 做完整哈希,不要传入已经哈希过的摘要。
func SignPSS(priv *rsa.PrivateKey, message []byte) ([]byte, error) {
if err := checkPrivateKey(priv); err != nil {
return nil, err
}
digest := sha256.Sum256(message)
signature, err := rsa.SignPSS(rand.Reader, priv, cryptoHash, digest[:], &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: cryptoHash,
})
if err != nil {
return nil, fmt.Errorf("rsax: PSS 签名失败: %w", err)
}
return signature, nil
}
// VerifyPSS 验证 RSASSA-PSS(SHA-256) 签名,成功返回 nil,失败返回非 nil 错误。
//
// pub 为签名者公钥,message 为原始消息,signature 为 SignPSS 的输出。
// 安全注意事项:验证失败(含长度不合法、填充错误、内容不匹配)统一包装
// rsa.ErrVerification 返回,调用方可用 errors.Is(err, rsa.ErrVerification) 判断;
// 盐长度参数在验证时按自动探测处理,以兼容其他实现产生的合法签名。
func VerifyPSS(pub *rsa.PublicKey, message, signature []byte) error {
if err := checkPublicKey(pub); err != nil {
return err
}
if len(signature) == 0 {
return fmt.Errorf("%w: 签名为空", ErrInvalidSignature)
}
if k := pub.Size(); len(signature) != k {
return fmt.Errorf("%w: 签名长度 %d 不是密钥长度 %d", ErrInvalidSignature, len(signature), k)
}
digest := sha256.Sum256(message)
if err := rsa.VerifyPSS(pub, cryptoHash, digest[:], signature, &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: cryptoHash,
}); err != nil {
return wrapVerifyError("PSS", err)
}
return nil
}
// SignPKCS1v15 使用 RSASSA-PKCS1-v1_5(SHA-256) 对消息签名。
//
// priv 为签名私钥,message 为待签名消息原文(函数内部完成哈希)。
// 安全注意事项:PKCS#1 v1.5 签名本身仍属可用方案,但缺乏 PSS 的可证明安全性;
// 新系统建议使用 SignPSS,本函数主要用于与既有系统互通。
func SignPKCS1v15(priv *rsa.PrivateKey, message []byte) ([]byte, error) {
if err := checkPrivateKey(priv); err != nil {
return nil, err
}
digest := sha256.Sum256(message)
signature, err := rsa.SignPKCS1v15(rand.Reader, priv, cryptoHash, digest[:])
if err != nil {
return nil, fmt.Errorf("rsax: PKCS#1 v1.5 签名失败: %w", err)
}
return signature, nil
}
// VerifyPKCS1v15 验证 RSASSA-PKCS1-v1_5(SHA-256) 签名,成功返回 nil,失败返回非 nil 错误。
//
// pub 为签名者公钥,message 为原始消息,signature 为 SignPKCS1v15 的输出。
// 安全注意事项:验证失败统一包装 rsa.ErrVerification 返回,
// 调用方不应向未认证的对端区分“签名格式错误”与“签名内容不匹配”。
func VerifyPKCS1v15(pub *rsa.PublicKey, message, signature []byte) error {
if err := checkPublicKey(pub); err != nil {
return err
}
if len(signature) == 0 {
return fmt.Errorf("%w: 签名为空", ErrInvalidSignature)
}
if k := pub.Size(); len(signature) != k {
return fmt.Errorf("%w: 签名长度 %d 不是密钥长度 %d", ErrInvalidSignature, len(signature), k)
}
digest := sha256.Sum256(message)
if err := rsa.VerifyPKCS1v15(pub, cryptoHash, digest[:], signature); err != nil {
return wrapVerifyError("PKCS#1 v1.5", err)
}
return nil
}
// wrapVerifyError 统一包装验签失败原因。
//
// scheme 为方案名(用于错误文本),err 为底层 verify 错误。
// 安全注意事项:不把底层库的细节暴露给未认证对端,同时保留 rsa.ErrVerification
// 的错误链,便于调用方用 errors.Is(err, rsa.ErrVerification) 精确判断“签名不匹配”。
// 这里刻意不回显签名或消息内容,避免敏感数据进入日志。
func wrapVerifyError(scheme string, err error) error {
if errors.Is(err, rsa.ErrVerification) {
return fmt.Errorf("%w: %s 验签失败: %w", ErrInvalidSignature, scheme, rsa.ErrVerification)
}
return fmt.Errorf("%w: %s 验签失败: %w", ErrInvalidSignature, scheme, err)
2023-12-27 19:00:14 +08:00
}