base-aes 🔐
零依赖 AES 加密基础库。
特性
- 支持 AES-ECB/CBC 模式加密/解密
- 完整 PKCS#7 填充方案实现
- 跨平台字节级数据转换工具
- 类型安全操作数组缓冲区
- Tree-shakable 模块化架构
安装
bash
npm add base-aes
bash
pnpm add base-aes
bash
yarn add base-aes
html
<script src="https://cdn.jsdelivr.net/npm/base-aes/dist/index.umd.min.js"></script>
<!-- 全局变量 BaseAes 可用 -->
核心 API
加密模式对比
模式 | 向量需求 | 安全性 | 适用场景 |
---|---|---|---|
ECB | ❌ 无需IV | ⚠️ 弱(相同明文块产生相同密文) | 快速加密/低安全性需求 |
CBC | ✅ 需要IV | ✅ 推荐 | 标准加密场景 |
加密模块
ts
// AES 基础模块
import { AES } from 'base-aes';
const cipher = new AES(key: Uint8Array);
cipher.encrypt(data: Uint8Array): Uint8Array;
cipher.decrypt(data: Uint8Array): Uint8Array;
// ECB 模式
import { ECB } from 'base-aes';
const ecb = new ECB(key: Uint8Array);
ecb.encrypt(data: Uint8Array): Uint8Array;
ecb.decrypt(data: Uint8Array): Uint8Array;
// CBC 模式
import { CBC } from 'base-aes';
const cbc = new CBC(key: Uint8Array, iv: Uint8Array);
cbc.encrypt(data: Uint8Array): Uint8Array;
cbc.decrypt(data: Uint8Array): Uint8Array;
数据转换工具
函数 | 功能 | 示例 |
---|---|---|
toUTF8Bytes | UTF-8 字符串转字节数组 | toUTF8Bytes('hello') |
fromUTF8Bytes | 字节数组转 UTF-8 字符串 | fromUTF8Bytes(bytes) |
toHexBytes | 十六进制字符串转字节数组 | toHexBytes('00ff') |
fromHexBytes | 字节数组转十六进制字符串 | fromHexBytes(bytes) |
填充方案
ts
import { padPKCS7Padding, stripPKCS7Padding } from 'base-aes';
const padded = padPKCS7Padding(data, blockSize); // 添加填充
const original = stripPKCS7Padding(padded); // 移除填充
使用示例
ECB 模式加密解密
ts
import { ECB, padPKCS7Padding, stripPKCS7Padding, toUTF8Bytes, fromUTF8Bytes, fromHexBytes } from 'base-aes';
// 生成密钥(16字节)
const key = toUTF8Bytes('\x00\x01\x02\x03\x04\x05\x06\x07\b\t\n\v\f\r\x0E\x0F');
// 待加密数据
const text = 'TextMustBe16Byte';
// 创建 ECB 实例
const ecb = new ECB(key);
// 加密数据
const encrypted = ecb.encrypt(padPKCS7Padding(toUTF8Bytes(text)));
console.log('密文:', fromHexBytes(encrypted));
// 密文: 61e6335e9518e20fd16aa30871e211e6954f64f2e4e86e9eee82d20216684899
// 解密数据
const decrypted = ecb.decrypt(encrypted);
console.log('明文:', fromUTF8Bytes(stripPKCS7Padding(decryptedBytes)));
// 明文: TextMustBe16Byte
CBC 模式加密解密
ts
import { CBC, padPKCS7Padding, stripPKCS7Padding, toUTF8Bytes, fromUTF8Bytes, fromHexBytes } from 'base-aes';
// 生成密钥和 IV(均为16字节)
const key = toUTF8Bytes('\x00\x01\x02\x03\x04\x05\x06\x07\b\t\n\v\f\r\x0E\x0F');
const iv = toUTF8Bytes('\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F !"#$');
const text = 'TextMustBe16Byte';
// 创建 CBC 实例
const cbc = new CBC(key, iv);
// 加密数据
const encrypted = cbc.encrypt(padPKCS7Padding(toUTF8Bytes(text)));
console.log('密文:', toHexBytes(encrypted));
// 密文: 0605fda3e80da8724d66811725a98f961bf3ca2e1fadf6af8f7223425c74bc69
// 解密数据
const decrypted = cbc.decrypt(encrypted);
console.log('明文:', fromUTF8Bytes(stripPKCS7Padding(decryptedBytes)));
// 明文: TextMustBe16Byte
对比 Go lang 实现
点击查看Golang代码
go
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"encoding/base64"
"fmt"
)
func main() {
// 测试向量 - AES-128-CBC 互操作性测试
key := []byte("\x00\x01\x02\x03\x04\x05\x06\x07\b\t\n\v\f\r\x0E\x0F")
iv := []byte("\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F !\"#$")
text := "TextMustBe16Byte"
ecbCipherText := "61e6335e9518e20fd16aa30871e211e6954f64f2e4e86e9eee82d20216684899"
cbcCipherText := "0605fda3e80da8724d66811725a98f961bf3ca2e1fadf6af8f7223425c74bc69"
// ECB 加密测试
ecbCipher := ECBEncrypt([]byte(text), key)
fmt.Printf("ECB 加密结果: %s\n", hex.EncodeToString(ecbCipher))
fmt.Printf("ECB 加密测试结果 %v\n", hex.EncodeToString(ecbCipher) == ecbCipherText)
fmt.Printf("\n")
// CBC 加密测试
cbcCipher := CBCEncrypt([]byte(text), key, iv)
fmt.Printf("CBC 加密结果: %s\n", hex.EncodeToString(cbcCipher))
fmt.Printf("CBC 加密测试结果 %v\n", hex.EncodeToString(cbcCipher) == cbcCipherText)
fmt.Printf("\n")
// 解密测试
fmt.Printf("ECB 解密结果: %s\n", string(ECBDecrypt(ecbCipher, key)))
fmt.Printf("CBC 解密结果: %s\n", string(CBCDecrypt(cbcCipher, key, iv)))
fmt.Printf("\n")
fmt.Println("base64编码密文", base64.StdEncoding.EncodeToString(cbcCipher))
// BgX9o+gNqHJNZoEXJamPlhvzyi4frfavj3IjQlx0vGk=
}
// AESEncryptECB AES-ECB 加密
func ECBEncrypt(plaintext []byte, key []byte) []byte {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// PKCS7 填充
padded := padPKCS7Padding(plaintext, block.BlockSize())
// 加密
ciphertext := make([]byte, len(padded))
for i := 0; i < len(padded); i += block.BlockSize() {
block.Encrypt(ciphertext[i:i+block.BlockSize()], padded[i:i+block.BlockSize()])
}
return ciphertext
}
// AESDecryptECB AES-ECB 解密
func ECBDecrypt(ciphertext []byte, key []byte) []byte {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
if len(ciphertext)%block.BlockSize() != 0 {
panic("ciphertext length is not a multiple of block size")
}
// 解密
plaintext := make([]byte, len(ciphertext))
for i := 0; i < len(ciphertext); i += block.BlockSize() {
block.Decrypt(plaintext[i:i+block.BlockSize()], ciphertext[i:i+block.BlockSize()])
}
// 去除填充
return stripPKCS7Padding(plaintext)
}
// CBCEncrypt AES-CBC 加密
func CBCEncrypt(plaintext []byte, key []byte, iv []byte) []byte {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// PKCS7 填充
padded := padPKCS7Padding(plaintext, block.BlockSize())
// CBC 加密
mode := cipher.NewCBCEncrypter(block, iv)
ciphertext := make([]byte, len(padded))
mode.CryptBlocks(ciphertext, padded)
return ciphertext
}
// CBCDecrypt AES-CBC 解密
func CBCDecrypt(ciphertext []byte, key []byte, iv []byte) []byte {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
if len(ciphertext)%block.BlockSize() != 0 {
panic("ciphertext length is not a multiple of block size")
}
// CBC 解密
mode := cipher.NewCBCDecrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
mode.CryptBlocks(plaintext, ciphertext)
// 去除填充
return stripPKCS7Padding(plaintext)
}
// PKCS7Padding PKCS#7 填充
func padPKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
if padding == 0 {
padding = blockSize
}
pad := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, pad...)
}
// stripPKCS7Padding 移除 PKCS#7 填充
func stripPKCS7Padding(data []byte) []byte {
if len(data) == 0 {
panic("empty data")
}
padding := int(data[len(data)-1])
if padding > len(data) || padding > aes.BlockSize {
panic("invalid padding")
}
return data[:len(data)-padding]
}