完善不同加密方法的使用
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
// Package cipherx 提供分组密码的通用封装。
|
||||
//
|
||||
// 设计目标:
|
||||
// - 统一 PKCS#7 填充、IV 生成与前缀格式、长度校验与错误语义;
|
||||
// - 消除 aesx / desx / tdesx / blowfishx 之间约 95% 的重复实现;
|
||||
// - 对来自外部的密文一律「先校验长度、再切片」,杜绝越界 panic。
|
||||
//
|
||||
// # 安全提示
|
||||
//
|
||||
// - ECB 模式不具备语义安全性:相同明文块会产生相同密文块,攻击者可据此
|
||||
// 识别重复内容与结构。该模式仅为兼容既有系统而保留,新代码不应使用。
|
||||
// - ECB / CBC / CFB / OFB / CTR **均不提供完整性保护**,密文被篡改时解密
|
||||
// 不会报错(CBC 的填充校验只能概率性发现篡改)。新代码应优先使用 AEAD,
|
||||
// 例如 AES-GCM 或 ChaCha20-Poly1305。若因兼容性必须使用这些模式,请自行
|
||||
// 采用「先加密后认证」(Encrypt-then-MAC)的组合方式。
|
||||
// - CBC 解密时所有填充校验失败均返回同一个 ErrInvalidPadding,且校验过程
|
||||
// 不提前短路,以免通过错误信息或响应时间区分失败原因(padding oracle)。
|
||||
package cipherx
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Mode 表示分组密码的工作模式。
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// ECB 电子密码本模式。需要填充;相同明文块产生相同密文块,不推荐使用。
|
||||
ECB Mode = iota
|
||||
// CBC 密码分组链接模式。需要填充,并需要随机且不可预测的 IV。
|
||||
CBC
|
||||
// CFB 密码反馈模式。不需要填充,按流式处理,解密不具有随机访问能力。
|
||||
CFB
|
||||
// OFB 输出反馈模式。不需要填充,按流式处理。
|
||||
OFB
|
||||
// CTR 计数器模式。不需要填充,按流式处理,可并行与随机访问。
|
||||
CTR
|
||||
)
|
||||
|
||||
var modeNames = [...]string{"ECB", "CBC", "CFB", "OFB", "CTR"}
|
||||
|
||||
// String 返回模式名称,便于日志与错误信息使用。
|
||||
func (m Mode) String() string {
|
||||
if !m.Valid() {
|
||||
return fmt.Sprintf("Mode(%d)", int(m))
|
||||
}
|
||||
return modeNames[m]
|
||||
}
|
||||
|
||||
// Valid 报告模式是否为已支持的模式。
|
||||
func (m Mode) Valid() bool { return int(m) >= 0 && int(m) < len(modeNames) }
|
||||
|
||||
// UsesIV 报告该模式是否需要初始化向量。
|
||||
func (m Mode) UsesIV() bool { return m != ECB }
|
||||
|
||||
// RequiresPadding 报告该模式是否需要 PKCS#7 填充。
|
||||
func (m Mode) RequiresPadding() bool { return m == ECB || m == CBC }
|
||||
|
||||
// 错误定义。调用方可用 errors.Is 精确判断失败类型。
|
||||
var (
|
||||
// ErrNilBlock 表示传入的 cipher.Block 为 nil。
|
||||
ErrNilBlock = errors.New("cipherx: cipher block is nil")
|
||||
// ErrInvalidBlockSize 表示块大小或填充块大小非法。
|
||||
ErrInvalidBlockSize = errors.New("cipherx: invalid block size")
|
||||
// ErrInvalidIVSize 表示 IV 长度与分组长度不一致。
|
||||
ErrInvalidIVSize = errors.New("cipherx: invalid IV size")
|
||||
// ErrInvalidMode 表示模式非法,或对该模式使用了不适用的操作(如给 ECB 传 IV)。
|
||||
ErrInvalidMode = errors.New("cipherx: invalid cipher mode")
|
||||
// ErrEmptyCiphertext 表示密文为空。
|
||||
ErrEmptyCiphertext = errors.New("cipherx: ciphertext is empty")
|
||||
// ErrCiphertextTooShort 表示密文长度不足以容纳 IV 或一个完整分组。
|
||||
ErrCiphertextTooShort = errors.New("cipherx: ciphertext is too short")
|
||||
// ErrNotBlockAligned 表示密文长度不是分组长度的整数倍。
|
||||
ErrNotBlockAligned = errors.New("cipherx: ciphertext is not a multiple of the block size")
|
||||
// ErrInvalidHex 表示十六进制文本无法解码。
|
||||
ErrInvalidHex = errors.New("cipherx: invalid hexadecimal input")
|
||||
// ErrInvalidPadding 统一表示 PKCS#7 填充校验失败。
|
||||
//
|
||||
// 刻意不区分「填充长度非法」「填充字节不一致」等具体原因:区分它们会让
|
||||
// 攻击者借助错误信息实施 padding oracle 攻击。
|
||||
ErrInvalidPadding = errors.New("cipherx: invalid padding")
|
||||
)
|
||||
|
||||
// PKCS7Pad 按 PKCS#7 规则填充 data,返回新切片(不修改入参)。
|
||||
//
|
||||
// blockSize 必须在 [1, 255] 范围内:填充长度需要用一个字节表示。
|
||||
// 若 len(data) 恰好是 blockSize 的整数倍,会额外填充一个完整分组。
|
||||
func PKCS7Pad(data []byte, blockSize int) ([]byte, error) {
|
||||
if blockSize <= 0 || blockSize > 255 {
|
||||
return nil, fmt.Errorf("cipherx: 填充块大小 %d 超出 [1,255]: %w", blockSize, ErrInvalidBlockSize)
|
||||
}
|
||||
padding := blockSize - len(data)%blockSize
|
||||
out := make([]byte, len(data)+padding)
|
||||
copy(out, data)
|
||||
for i := len(data); i < len(out); i++ {
|
||||
out[i] = byte(padding)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// PKCS7Unpad 校验并去除 PKCS#7 填充。
|
||||
//
|
||||
// 所有失败原因统一返回 ErrInvalidPadding;校验循环不提前中断,以减小时序差异。
|
||||
// 返回的切片是新分配的,不与入参共享底层数组。
|
||||
func PKCS7Unpad(data []byte, blockSize int) ([]byte, error) {
|
||||
if blockSize <= 0 || blockSize > 255 {
|
||||
return nil, fmt.Errorf("cipherx: 填充块大小 %d 超出 [1,255]: %w", blockSize, ErrInvalidBlockSize)
|
||||
}
|
||||
length := len(data)
|
||||
if length == 0 || length%blockSize != 0 {
|
||||
return nil, ErrInvalidPadding
|
||||
}
|
||||
|
||||
padding := int(data[length-1])
|
||||
if padding == 0 || padding > blockSize || padding > length {
|
||||
return nil, ErrInvalidPadding
|
||||
}
|
||||
|
||||
// 恒定时间风格的整块比较:即使发现不一致也继续遍历完,避免时序泄漏。
|
||||
var diff byte
|
||||
for _, b := range data[length-padding:] {
|
||||
diff |= b ^ byte(padding)
|
||||
}
|
||||
if diff != 0 {
|
||||
return nil, ErrInvalidPadding
|
||||
}
|
||||
|
||||
out := make([]byte, length-padding)
|
||||
copy(out, data[:length-padding])
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NewIV 用 crypto/rand 生成一个长度为 blockSize 的随机 IV。
|
||||
func NewIV(blockSize int) ([]byte, error) {
|
||||
if blockSize <= 0 {
|
||||
return nil, fmt.Errorf("cipherx: 块大小 %d 非法: %w", blockSize, ErrInvalidBlockSize)
|
||||
}
|
||||
iv := make([]byte, blockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, fmt.Errorf("cipherx: 生成随机 IV 失败: %w", err)
|
||||
}
|
||||
return iv, nil
|
||||
}
|
||||
|
||||
func validateBlock(block cipher.Block) error {
|
||||
if block == nil {
|
||||
return ErrNilBlock
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMode(mode Mode) error {
|
||||
if !mode.Valid() {
|
||||
return fmt.Errorf("cipherx: 不支持的模式 %d: %w", int(mode), ErrInvalidMode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt 使用给定分组与模式加密明文,IV 由本函数随机生成。
|
||||
//
|
||||
// 返回值格式:
|
||||
// - ECB:密文(已做 PKCS#7 填充)
|
||||
// - 其余模式:iv || 密文(IV 前置,便于随密文一起存储与传输)
|
||||
func Encrypt(block cipher.Block, mode Mode, plaintext []byte) ([]byte, error) {
|
||||
if err := validateBlock(block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateMode(mode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mode == ECB {
|
||||
return encryptECB(block, plaintext)
|
||||
}
|
||||
|
||||
iv, err := NewIV(block.BlockSize())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := EncryptWithIV(block, mode, plaintext, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]byte, 0, len(iv)+len(body))
|
||||
out = append(out, iv...)
|
||||
out = append(out, body...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Decrypt 解密由 Encrypt 产生的数据(IV 前置格式)。
|
||||
func Decrypt(block cipher.Block, mode Mode, ciphertext []byte) ([]byte, error) {
|
||||
if err := validateBlock(block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateMode(mode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mode == ECB {
|
||||
return decryptECB(block, ciphertext)
|
||||
}
|
||||
|
||||
bs := block.BlockSize()
|
||||
if len(ciphertext) < bs {
|
||||
return nil, fmt.Errorf("cipherx: %s 密文长度 %d 不足以容纳 %d 字节 IV: %w",
|
||||
mode, len(ciphertext), bs, ErrCiphertextTooShort)
|
||||
}
|
||||
iv := ciphertext[:bs]
|
||||
body := ciphertext[bs:]
|
||||
return DecryptWithIV(block, mode, body, iv)
|
||||
}
|
||||
|
||||
// EncryptWithIV 使用调用方提供的 IV 加密,返回值**只包含密文本身**(不前置 IV)。
|
||||
//
|
||||
// 该函数用于与规定了固定 IV 的既有系统互操作。自行管理 IV 时必须保证:
|
||||
// - IV 在同一密钥下绝不重复(CBC/CFB 要求不可预测,CTR/OFB 要求唯一);
|
||||
// - IV 无需保密,但应与密文一同传递。
|
||||
//
|
||||
// ECB 模式不接受 IV,调用会返回 ErrInvalidMode。
|
||||
func EncryptWithIV(block cipher.Block, mode Mode, plaintext, iv []byte) ([]byte, error) {
|
||||
if err := validateBlock(block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateMode(mode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mode == ECB {
|
||||
return nil, fmt.Errorf("cipherx: ECB 模式不接受 IV: %w", ErrInvalidMode)
|
||||
}
|
||||
|
||||
bs := block.BlockSize()
|
||||
if len(iv) != bs {
|
||||
return nil, fmt.Errorf("cipherx: IV 长度 %d, 期望 %d: %w", len(iv), bs, ErrInvalidIVSize)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case CBC:
|
||||
padded, err := PKCS7Pad(plaintext, bs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]byte, len(padded))
|
||||
cipher.NewCBCEncrypter(block, iv).CryptBlocks(out, padded)
|
||||
return out, nil
|
||||
case CFB:
|
||||
out := make([]byte, len(plaintext))
|
||||
//lint:ignore SA1019 CFB 自 Go 1.24 起被标准库标记为废弃(无认证);本库保留它是为了与既有系统互操作,包注释已明确警告其不提供完整性保护。
|
||||
cipher.NewCFBEncrypter(block, iv).XORKeyStream(out, plaintext)
|
||||
return out, nil
|
||||
case OFB:
|
||||
out := make([]byte, len(plaintext))
|
||||
//lint:ignore SA1019 同上:OFB 仅为兼容既有系统而保留,不提供完整性保护。
|
||||
cipher.NewOFB(block, iv).XORKeyStream(out, plaintext)
|
||||
return out, nil
|
||||
case CTR:
|
||||
out := make([]byte, len(plaintext))
|
||||
cipher.NewCTR(block, iv).XORKeyStream(out, plaintext)
|
||||
return out, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cipherx: 不支持的模式 %d: %w", int(mode), ErrInvalidMode)
|
||||
}
|
||||
|
||||
// DecryptWithIV 使用调用方提供的 IV 解密 EncryptWithIV 产生的密文。
|
||||
func DecryptWithIV(block cipher.Block, mode Mode, ciphertext, iv []byte) ([]byte, error) {
|
||||
if err := validateBlock(block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateMode(mode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mode == ECB {
|
||||
return nil, fmt.Errorf("cipherx: ECB 模式不接受 IV: %w", ErrInvalidMode)
|
||||
}
|
||||
|
||||
bs := block.BlockSize()
|
||||
if len(iv) != bs {
|
||||
return nil, fmt.Errorf("cipherx: IV 长度 %d, 期望 %d: %w", len(iv), bs, ErrInvalidIVSize)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case CBC:
|
||||
if len(ciphertext) == 0 {
|
||||
return nil, ErrEmptyCiphertext
|
||||
}
|
||||
if len(ciphertext)%bs != 0 {
|
||||
return nil, fmt.Errorf("cipherx: CBC 密文长度 %d: %w", len(ciphertext), ErrNotBlockAligned)
|
||||
}
|
||||
out := make([]byte, len(ciphertext))
|
||||
cipher.NewCBCDecrypter(block, iv).CryptBlocks(out, ciphertext)
|
||||
return PKCS7Unpad(out, bs)
|
||||
case CFB:
|
||||
out := make([]byte, len(ciphertext))
|
||||
//lint:ignore SA1019 CFB 自 Go 1.24 起被标准库标记为废弃(无认证);本库保留它是为了与既有系统互操作,包注释已明确警告其不提供完整性保护。
|
||||
cipher.NewCFBDecrypter(block, iv).XORKeyStream(out, ciphertext)
|
||||
return out, nil
|
||||
case OFB:
|
||||
out := make([]byte, len(ciphertext))
|
||||
//lint:ignore SA1019 同上:OFB 仅为兼容既有系统而保留,不提供完整性保护。
|
||||
cipher.NewOFB(block, iv).XORKeyStream(out, ciphertext)
|
||||
return out, nil
|
||||
case CTR:
|
||||
out := make([]byte, len(ciphertext))
|
||||
cipher.NewCTR(block, iv).XORKeyStream(out, ciphertext)
|
||||
return out, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cipherx: 不支持的模式 %d: %w", int(mode), ErrInvalidMode)
|
||||
}
|
||||
|
||||
func encryptECB(block cipher.Block, plaintext []byte) ([]byte, error) {
|
||||
bs := block.BlockSize()
|
||||
padded, err := PKCS7Pad(plaintext, bs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]byte, len(padded))
|
||||
for i := 0; i < len(padded); i += bs {
|
||||
block.Encrypt(out[i:i+bs], padded[i:i+bs])
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func decryptECB(block cipher.Block, ciphertext []byte) ([]byte, error) {
|
||||
bs := block.BlockSize()
|
||||
if len(ciphertext) == 0 {
|
||||
return nil, ErrEmptyCiphertext
|
||||
}
|
||||
// 先校验长度再切片:旧实现缺少这一步,畸形密文会直接触发
|
||||
// "slice bounds out of range" panic,构成可远程触发的拒绝服务。
|
||||
if len(ciphertext)%bs != 0 {
|
||||
return nil, fmt.Errorf("cipherx: ECB 密文长度 %d: %w", len(ciphertext), ErrNotBlockAligned)
|
||||
}
|
||||
out := make([]byte, len(ciphertext))
|
||||
for i := 0; i < len(ciphertext); i += bs {
|
||||
block.Decrypt(out[i:i+bs], ciphertext[i:i+bs])
|
||||
}
|
||||
return PKCS7Unpad(out, bs)
|
||||
}
|
||||
|
||||
// EncryptHex 与 Encrypt 相同,但把结果编码为**大写**十六进制字符串。
|
||||
func EncryptHex(block cipher.Block, mode Mode, plaintext []byte) (string, error) {
|
||||
ciphertext, err := Encrypt(block, mode, plaintext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.ToUpper(hex.EncodeToString(ciphertext)), nil
|
||||
}
|
||||
|
||||
// DecryptHex 解密十六进制密文,大小写均可,允许首尾空白。
|
||||
func DecryptHex(block cipher.Block, mode Mode, ciphertextHex string) ([]byte, error) {
|
||||
ciphertext, err := decodeHex(ciphertextHex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Decrypt(block, mode, ciphertext)
|
||||
}
|
||||
|
||||
// EncryptWithIVHex 与 EncryptWithIV 相同,返回大写十六进制字符串。
|
||||
func EncryptWithIVHex(block cipher.Block, mode Mode, plaintext, iv []byte) (string, error) {
|
||||
ciphertext, err := EncryptWithIV(block, mode, plaintext, iv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.ToUpper(hex.EncodeToString(ciphertext)), nil
|
||||
}
|
||||
|
||||
// DecryptWithIVHex 解密十六进制密文,使用调用方提供的 IV。
|
||||
func DecryptWithIVHex(block cipher.Block, mode Mode, ciphertextHex string, iv []byte) ([]byte, error) {
|
||||
ciphertext, err := decodeHex(ciphertextHex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DecryptWithIV(block, mode, ciphertext, iv)
|
||||
}
|
||||
|
||||
func decodeHex(s string) ([]byte, error) {
|
||||
decoded, err := hex.DecodeString(strings.TrimSpace(s))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrInvalidHex, err)
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
Reference in New Issue
Block a user