Files
encryptx/encryptx.go
T
2026-09-30 15:49:12 +08:00

483 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package encryptx 提供一个统一的、字符串驱动的加解密门面,覆盖哈希、对称加密、
// 消息认证码、密钥派生、口令哈希、非对称加密与数字签名。
//
// # 定位与建议
//
// 本包适合「算法由配置/管理后台决定」的场景:调用方从配置里读到算法名,转成
// EncryptType 后交给 Encrypt/Decrypt 即可,无需为每种算法写分支。
//
// 如果你在写业务代码而不是配置驱动的调度代码,**优先直接使用子包**
// (aesx、chachax、rsax、ed25519x、argon2x 等):它们有强类型的参数、
// 更明确的错误,并且不需要在运行时解析选项。
//
// # 密钥约定
//
// 密钥一律是 []byte,绝不使用字符串:
//
// - 对称算法、HMAC、KDF:key 是**原始密钥字节**(不是十六进制、不是 Base64)。
// 用相应子包的 GenerateKey 获取;
// - RSA / Ed25519 / ECDSA:key 是 **PEM 编码的密钥内容**(公钥用于加密/验签,
// 私钥用于解密/签名)。本包不会读取文件,需要从文件加载时请调用
// rsax.LoadPublicKey 等函数。
//
// 历史版本把密钥当字符串并直接按字节使用,而 GenerateKey 返回的却是十六进制
// 文本,导致「生成密钥 → 加密」这条路径必然失败。统一为 []byte 后这类错误在
// 类型层面就不可能出现。
//
// # 输出格式
//
// 不同算法族的输出编码不同,这是为了与既有系统保持一致:
//
// - 哈希 / HMAC / HEX 编码:小写十六进制;
// - Base64 编码:标准 Base64(带填充);
// - 分组密码模式(ECB/CBC/CFB/OFB/CTR):**大写**十六进制,且不含换行;
// - AEAD(AES-GCM、ChaCha20-Poly1305):"密文十六进制:nonce十六进制"(小写、
// 无换行),两者以冒号分隔;
// - RSA 加密、数字签名:标准 Base64。
package encryptx
import (
"errors"
"fmt"
"sort"
)
// EncryptType 标识一种密码学算法。
//
// 枚举值显式赋值而非常量递增(iota),因为历史版本用的是连续 iota:一旦在中间
// 插入新算法,所有后续算法的数值都会平移,导致已经落库的配置指向错误的算法。
// 这里保留原有数值不变,新算法一律追加在末尾。
type EncryptType int
// 哈希算法。
const (
MD5 EncryptType = 0
SHA1 EncryptType = 1
SHA256 EncryptType = 2
SHA512 EncryptType = 3
SHA3_256 EncryptType = 4
SHA3_384 EncryptType = 5
SHA3_512 EncryptType = 6
)
// 对称加密算法(历史数值区间)。
const (
DES_ECB EncryptType = 7
DES_CBC EncryptType = 8
DES_CFB EncryptType = 9
TDES_ECB EncryptType = 10
TDES_CBC EncryptType = 11
TDES_CFB EncryptType = 12
AES_ECB EncryptType = 13
AES_CBC EncryptType = 14
AES_CFB EncryptType = 15
AES_GCM EncryptType = 16
BLOWFISH_ECB EncryptType = 17
BLOWFISH_CBC EncryptType = 18
BLOWFISH_CFB EncryptType = 19
)
// 非对称加密、消息认证码、密钥派生与编码(历史数值区间)。
const (
RSA EncryptType = 20
HMAC_SHA256 EncryptType = 21
HMAC_SHA512 EncryptType = 22
PBKDF2 EncryptType = 23
BASE64 EncryptType = 24
)
// 以下为新增算法,从 25 开始追加,保证既有数值稳定。
const (
// 哈希
SHA224 EncryptType = 25
SHA384 EncryptType = 26
SHA3_224 EncryptType = 27
BLAKE2B_256 EncryptType = 28
BLAKE2S_256 EncryptType = 29
RIPEMD160 EncryptType = 30
// 对称加密(补齐 OFB/CTR 与 AEAD)
DES_OFB EncryptType = 31
DES_CTR EncryptType = 32
TDES_OFB EncryptType = 33
TDES_CTR EncryptType = 34
AES_OFB EncryptType = 35
AES_CTR EncryptType = 36
BLOWFISH_OFB EncryptType = 37
BLOWFISH_CTR EncryptType = 38
CHACHA20_POLY1305 EncryptType = 39
XCHACHA20_POLY1305 EncryptType = 40
// 消息认证码
HMAC_SHA1 EncryptType = 41
HMAC_SHA384 EncryptType = 42
// 密钥派生与口令哈希
HKDF EncryptType = 43
SCRYPT EncryptType = 44
ARGON2ID EncryptType = 45
BCRYPT EncryptType = 46
// 非对称加密(OAEP 为现代默认)
RSA_OAEP EncryptType = 47
// 编码
BASE64_URL EncryptType = 48
HEX EncryptType = 49
)
// SignType 标识一种数字签名算法。
//
// 签名与加密的语义不同(签名用私钥、验签用公钥,且不涉及机密性),因此使用
// 独立的枚举与独立的 Sign/Verify 入口,而不是塞进 EncryptType。
type SignType int
const (
// ED25519 是当前首选的签名算法:实现简单、无参数、签名确定、抗侧信道。
ED25519 SignType = iota
// ECDSA_P256 是 NIST P-256 曲线上的 ECDSA。
ECDSA_P256
// ECDSA_P384 是 NIST P-384 曲线上的 ECDSA。
ECDSA_P384
// ECDSA_P521 是 NIST P-521 曲线上的 ECDSA。
ECDSA_P521
// RSA_PSS 是带 PSS 填充的 RSA 签名,是现代推荐形式。
RSA_PSS
// RSA_PKCS1V15 是传统的 PKCS#1 v1.5 签名,仅为兼容旧系统保留。
RSA_PKCS1V15
)
var signTypeNames = map[SignType]string{
ED25519: "ED25519",
ECDSA_P256: "ECDSA-P256",
ECDSA_P384: "ECDSA-P384",
ECDSA_P521: "ECDSA-P521",
RSA_PSS: "RSA-PSS",
RSA_PKCS1V15: "RSA-PKCS1v15",
}
// String 返回签名算法名称。
func (s SignType) String() string {
if name, ok := signTypeNames[s]; ok {
return name
}
return fmt.Sprintf("SignType(%d)", int(s))
}
// Valid 报告签名算法是否受支持。
func (s SignType) Valid() bool {
_, ok := signTypeNames[s]
return ok
}
// UsesRSA 报告该签名算法是否基于 RSA(密钥格式为 RSA PEM)。
func (s SignType) UsesRSA() bool {
return s == RSA_PSS || s == RSA_PKCS1V15
}
// GetAllSignTypes 返回全部签名算法,按数值升序。
func GetAllSignTypes() []SignType {
types := make([]SignType, 0, len(signTypeNames))
for t := range signTypeNames {
types = append(types, t)
}
sort.Slice(types, func(i, j int) bool { return int(types[i]) < int(types[j]) })
return types
}
// SecurityLevel 表示算法的安全等级,供管理界面给出风险提示或禁止选择。
type SecurityLevel int
const (
// SecurityBroken 表示算法已被实际攻破,任何情况下都不应使用。
SecurityBroken SecurityLevel = iota
// SecurityLegacy 表示算法未被攻破但已不推荐,仅用于与既有系统互操作。
SecurityLegacy
// SecurityAcceptable 表示算法仍可接受,但存在更好的现代替代。
SecurityAcceptable
// SecurityRecommended 表示当前推荐使用的算法。
SecurityRecommended
)
// String 返回安全等级的中文说明。
func (l SecurityLevel) String() string {
switch l {
case SecurityBroken:
return "已攻破"
case SecurityLegacy:
return "遗留"
case SecurityAcceptable:
return "可接受"
case SecurityRecommended:
return "推荐"
default:
return fmt.Sprintf("SecurityLevel(%d)", int(l))
}
}
// typeInfo 描述一种算法的元数据。集中放在一张表里,便于 GetAllEncryptTypes
// 与界面直接消费,也避免在多处 switch 里各写一遍。
type typeInfo struct {
name string
level SecurityLevel
reversible bool // 是否可逆(能否 Decrypt)
needsKey bool // 是否需要密钥
aead bool // 是否提供完整性保护(AEAD)
note string
}
var typeInfos = map[EncryptType]typeInfo{
// ---- 哈希(不可逆、不需要密钥)----
MD5: {"MD5", SecurityBroken, false, false, false, "已被实际碰撞攻击攻破,仅可用于非安全的校验和"},
SHA1: {"SHA1", SecurityBroken, false, false, false, "已被 SHAttered 碰撞攻击攻破,仅可用于非安全的校验和"},
SHA224: {"SHA-224", SecurityAcceptable, false, false, false, "SHA-256 的截断版本,输出 28 字节"},
SHA256: {"SHA-256", SecurityRecommended, false, false, false, ""},
SHA384: {"SHA-384", SecurityRecommended, false, false, false, "SHA-512 的截断版本,输出 48 字节"},
SHA512: {"SHA-512", SecurityRecommended, false, false, false, ""},
SHA3_224: {"SHA3-224", SecurityAcceptable, false, false, false, ""},
SHA3_256: {"SHA3-256", SecurityRecommended, false, false, false, "与 SHA-256 无继承关系,抗长度扩展攻击"},
SHA3_384: {"SHA3-384", SecurityRecommended, false, false, false, ""},
SHA3_512: {"SHA3-512", SecurityRecommended, false, false, false, ""},
BLAKE2B_256: {"BLAKE2b-256", SecurityRecommended, false, false, false, "速度快于 SHA-2,适合大数据量校验"},
BLAKE2S_256: {"BLAKE2s-256", SecurityRecommended, false, false, false, "面向 32 位平台优化的 BLAKE2"},
RIPEMD160: {"RIPEMD-160", SecurityLegacy, false, false, false, "仅用于兼容旧系统(如比特币地址)"},
// ---- 对称加密 ----
DES_ECB: {"DES-ECB", SecurityBroken, true, true, false, "56 位密钥可被暴力破解,且 ECB 无语义安全性"},
DES_CBC: {"DES-CBC", SecurityBroken, true, true, false, "56 位密钥可被暴力破解"},
DES_CFB: {"DES-CFB", SecurityBroken, true, true, false, "56 位密钥可被暴力破解"},
DES_OFB: {"DES-OFB", SecurityBroken, true, true, false, "56 位密钥可被暴力破解"},
DES_CTR: {"DES-CTR", SecurityBroken, true, true, false, "56 位密钥可被暴力破解"},
TDES_ECB: {"3DES-ECB", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险,且 ECB 无语义安全性"},
TDES_CBC: {"3DES-CBC", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险"},
TDES_CFB: {"3DES-CFB", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险"},
TDES_OFB: {"3DES-OFB", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险"},
TDES_CTR: {"3DES-CTR", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险"},
BLOWFISH_ECB: {"Blowfish-ECB", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险,且 ECB 无语义安全性"},
BLOWFISH_CBC: {"Blowfish-CBC", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险"},
BLOWFISH_CFB: {"Blowfish-CFB", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险"},
BLOWFISH_OFB: {"Blowfish-OFB", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险"},
BLOWFISH_CTR: {"Blowfish-CTR", SecurityLegacy, true, true, false, "64 位分组存在 Sweet32 风险"},
AES_ECB: {"AES-ECB", SecurityLegacy, true, true, false, "ECB 无语义安全性,相同明文块产生相同密文块"},
AES_CBC: {"AES-CBC", SecurityAcceptable, true, true, false, "无完整性保护;建议改用 AES-GCM"},
AES_CFB: {"AES-CFB", SecurityAcceptable, true, true, false, "无完整性保护;建议改用 AES-GCM"},
AES_OFB: {"AES-OFB", SecurityAcceptable, true, true, false, "无完整性保护;建议改用 AES-GCM"},
AES_CTR: {"AES-CTR", SecurityAcceptable, true, true, false, "无完整性保护;建议改用 AES-GCM"},
AES_GCM: {"AES-GCM", SecurityRecommended, true, true, true, "推荐;务必保证 nonce 在同一密钥下不重复"},
CHACHA20_POLY1305: {"ChaCha20-Poly1305", SecurityRecommended, true, true, true, "推荐;无 AES 硬件加速时的首选"},
XCHACHA20_POLY1305: {"XChaCha20-Poly1305", SecurityRecommended, true, true, true, "推荐;24 字节 nonce 可安全随机生成"},
// ---- 消息认证码 ----
HMAC_SHA1: {"HMAC-SHA1", SecurityLegacy, false, true, true, "HMAC 本身仍安全,但底层 SHA-1 已不推荐"},
HMAC_SHA256: {"HMAC-SHA256", SecurityRecommended, false, true, true, ""},
HMAC_SHA384: {"HMAC-SHA384", SecurityRecommended, false, true, true, ""},
HMAC_SHA512: {"HMAC-SHA512", SecurityRecommended, false, true, true, ""},
// ---- 密钥派生与口令哈希 ----
PBKDF2: {"PBKDF2-HMAC-SHA256", SecurityAcceptable, false, false, false, "迭代次数需按硬件能力调整,建议 ≥ 600000"},
HKDF: {"HKDF-SHA256", SecurityRecommended, false, false, false, "用于高熵密钥材料的派生与分离,不适合低熵口令"},
SCRYPT: {"scrypt", SecurityRecommended, false, false, false, "内存硬,适合口令存储;Encrypt 走原始密钥派生,需显式提供 salt"},
ARGON2ID: {"Argon2id", SecurityRecommended, false, false, false, "当前口令哈希的首选;Encrypt 走 PHC 口令哈希,DeriveKey 走原始密钥派生"},
BCRYPT: {"bcrypt", SecurityRecommended, false, false, false, "经过长期考验;内存开销较小"},
// ---- 非对称 ----
RSA: {"RSA-OAEP(SHA-256)", SecurityRecommended, true, true, true, "带 OAEP 填充,支持长明文自动分块"},
RSA_OAEP: {"RSA-OAEP(SHA-256)", SecurityRecommended, true, true, true, "与 RSA 等价,保留独立取值便于配置区分"},
// ---- 编码 ----
BASE64: {"Base64", SecurityRecommended, true, false, false, "不是加密,仅编码"},
BASE64_URL: {"Base64-URL", SecurityRecommended, true, false, false, "URL 与文件名安全的 Base64"},
HEX: {"Hex", SecurityRecommended, true, false, false, "不是加密,仅编码"},
}
// String 返回算法名称;未知取值返回带数值的占位串。
func (t EncryptType) String() string {
if info, ok := typeInfos[t]; ok {
return info.name
}
return fmt.Sprintf("EncryptType(%d)", int(t))
}
// Valid 报告该算法是否受支持。
func (t EncryptType) Valid() bool {
_, ok := typeInfos[t]
return ok
}
// SecurityLevel 返回算法的安全等级。
func (t EncryptType) SecurityLevel() SecurityLevel {
if info, ok := typeInfos[t]; ok {
return info.level
}
return SecurityBroken
}
// IsReversible 报告算法是否可逆(即能否调用 Decrypt 还原)。
func (t EncryptType) IsReversible() bool {
info, ok := typeInfos[t]
return ok && info.reversible
}
// NeedsKey 报告算法是否需要密钥。
func (t EncryptType) NeedsKey() bool {
info, ok := typeInfos[t]
return ok && info.needsKey
}
// IsAEAD 报告算法是否提供完整性保护(AEAD 或消息认证码)。
func (t EncryptType) IsAEAD() bool {
info, ok := typeInfos[t]
return ok && info.aead
}
// Note 返回该算法的补充说明(安全提示、使用建议),可能为空。
func (t EncryptType) Note() string {
return typeInfos[t].note
}
// IsDeprecated 报告算法是否已不推荐使用(含"已攻破"与"遗留"两级)。
func (t EncryptType) IsDeprecated() bool {
return t.SecurityLevel() <= SecurityLegacy
}
// GetEncryptTypeName 返回算法名称,兼容历史 API。
//
// 新代码请直接使用 EncryptType.String。
func GetEncryptTypeName(encryptType EncryptType) string {
return encryptType.String()
}
// GetAllEncryptTypes 返回全部受支持的算法,按数值升序。
func GetAllEncryptTypes() []EncryptType {
types := make([]EncryptType, 0, len(typeInfos))
for t := range typeInfos {
types = append(types, t)
}
sort.Slice(types, func(i, j int) bool { return int(types[i]) < int(types[j]) })
return types
}
// 门面层错误。调用方可用 errors.Is 精确判断。
var (
// ErrUnsupportedType 表示算法取值不在支持范围内。
ErrUnsupportedType = errors.New("encryptx: unsupported algorithm")
// ErrIrreversible 表示对所给算法调用了 Decrypt,但该算法不可逆。
ErrIrreversible = errors.New("encryptx: algorithm is not reversible")
// ErrMissingKey 表示算法需要密钥但未提供。
ErrMissingKey = errors.New("encryptx: key is required")
// ErrMissingOption 表示缺少必需的选项。
ErrMissingOption = errors.New("encryptx: required option is missing")
// ErrUnknownOption 表示传入了该算法不认识的选项。
//
// 历史版本会静默忽略拼错或多余的选项,导致「以为设置了参数、实际用的却是
// 默认值」这类安全隐患。现在一律报错。
ErrUnknownOption = errors.New("encryptx: unknown option")
// ErrInvalidOption 表示选项类型不正确。
ErrInvalidOption = errors.New("encryptx: invalid option type")
// ErrInvalidInput 表示输入数据格式非法(如非法的十六进制密文、长度不足的密文)。
ErrInvalidInput = errors.New("encryptx: invalid input")
// ErrUnexpectedKey 表示给一个不使用密钥的算法传入了密钥。
//
// 典型场景:把 BASE64、哈希或口令哈希误当成加密算法,却仍然按加密的方式
// 传了密钥。历史版本会静默忽略这个密钥,让调用方以为数据受到了保护。
ErrUnexpectedKey = errors.New("encryptx: unexpected key for a keyless algorithm")
)
// Options 承载算法的额外参数。
//
// 取值务必使用与本包配套的构造函数(如 IntOption、BytesOption)或直接传
// int/string/bool/[]byte,以免类型不符。所有取值都会在解析时校验:
// 类型不对或键名不认识都会返回错误,不再静默回退到默认值。
type Options map[string]any
// Int 读取一个整数选项。第二个返回值表示该键是否存在。
//
// 接受 int、int64,以及能精确表示为整数的 float64(便于处理 JSON 解码结果)。
func (o Options) Int(key string) (int, bool, error) {
raw, ok := o[key]
if !ok {
return 0, false, nil
}
switch v := raw.(type) {
case int:
return v, true, nil
case int64:
return int(v), true, nil
case float64:
if v != float64(int(v)) {
return 0, false, fmt.Errorf("%w: 选项 %q 的值 %v 不是整数", ErrInvalidOption, key, v)
}
return int(v), true, nil
default:
return 0, false, fmt.Errorf("%w: 选项 %q 期望整数, 得到 %T", ErrInvalidOption, key, raw)
}
}
// String 读取一个字符串选项。
func (o Options) String(key string) (string, bool, error) {
raw, ok := o[key]
if !ok {
return "", false, nil
}
v, ok := raw.(string)
if !ok {
return "", false, fmt.Errorf("%w: 选项 %q 期望字符串, 得到 %T", ErrInvalidOption, key, raw)
}
return v, true, nil
}
// Bool 读取一个布尔选项。
func (o Options) Bool(key string) (bool, bool, error) {
raw, ok := o[key]
if !ok {
return false, false, nil
}
v, ok := raw.(bool)
if !ok {
return false, false, fmt.Errorf("%w: 选项 %q 期望布尔值, 得到 %T", ErrInvalidOption, key, raw)
}
return v, true, nil
}
// Bytes 读取一个二进制选项,接受 []byte 或十六进制/Base64 字符串。
func (o Options) Bytes(key string) ([]byte, bool, error) {
raw, ok := o[key]
if !ok {
return nil, false, nil
}
switch v := raw.(type) {
case []byte:
return v, true, nil
case string:
decoded, err := decodeOptionBytes(key, v)
if err != nil {
return nil, false, err
}
return decoded, true, nil
default:
return nil, false, fmt.Errorf("%w: 选项 %q 期望 []byte 或字符串, 得到 %T", ErrInvalidOption, key, raw)
}
}
// rejectUnknown 校验没有传入未声明的选项,避免参数被静默忽略。
func (o Options) rejectUnknown(allowed ...string) error {
if len(o) == 0 {
return nil
}
known := make(map[string]struct{}, len(allowed))
for _, k := range allowed {
known[k] = struct{}{}
}
var unknown []string
for k := range o {
if _, ok := known[k]; !ok {
unknown = append(unknown, k)
}
}
if len(unknown) > 0 {
sort.Strings(unknown)
return fmt.Errorf("%w: %v (该算法接受的选项: %v)", ErrUnknownOption, unknown, allowed)
}
return nil
}