AES-CBC + HMAC-SHA256 加密验证方案,下面是该方案二等 优点 与 缺点 表格,适用于文档、评审或技术选型说明。
✅ 优点表格:AES-CBC + HMAC-SHA256 加密验证方案
类别 优点 说明 🔐 安全性 使用 AES-CBC 对称加密 使用 AES-128-CBC 是可靠且广泛接受的对称加密方式。 随机生成 IV 每次加密生成新的 IV,有效防止密文重放与模式识别。 HMAC-SHA256 签名 增强完整性校验,防止中间人篡改密文。 加密前先签名验证 防止不合法密文触发解密报错或被利用。 💡 灵活性 签名算法可切换 支持从 HMAC-SHA256 切换为其他如 SHA-512。 密钥可由 token 派生 动态生成密钥,便于用户级安全控制。 前端跨平台适用 适用于 Web、小程序、移动端等多平台前端环境。 🔁 可部署性 可嵌入代理层 Nginx + Lua 可提前拦截无效请求,节省后端资源。 🧩 多语言兼容 Node.js、Python、Lua 等实现简单 支持常见语言和平台,易于团队协作与系统整合。
❌ 缺点表格:AES-CBC + HMAC 签名方案的局限
类别 缺点 说明 ⚙️ 实现复杂度 实现逻辑较多 需要额外编码 IV 管理、HMAC 签名、前后端一致性等细节。 🔁 重放防护 默认无时间戳或 nonce 重放攻击不可防,需要自行引入 timestamp + nonce
参数。 🔑 密钥依赖 密钥动态性带来兼容问题 一旦 token 失效或更换,旧数据将无法解密。 📦 数据随机访问 不支持局部解密 AES-CBC 是块加密,不能随机访问或解密数据片段。 🕒 不适合长期缓存 密文随机性增加校验复杂度 每次加密结果不同,不适合用于长期静态存储的校验场景。
🧭 补充建议(可选扩展)
增强点 建议 防重放 在签名前加上时间戳 + nonce 字段,防止多次使用旧数据 加密模式升级 可考虑迁移到 AES-GCM,天然支持认证(AEAD) 秘钥管理 密钥建议动态派生(如基于用户会话、JWT claims 等)
下面是该方案的实现详细代码:
✅ 前端 JavaScript:frontend.js
import aesjs from 'aes-js' ;
import CryptoJS from 'crypto-js' ;
function aaa ( token ) {
return aesjs. utils. utf8. toBytes ( token. padEnd ( 16 , '0' ) . slice ( 0 , 16 ) ) ;
}
function generateRandomIV ( ) {
let iv = new Uint8Array ( 16 ) ;
window. crypto. getRandomValues ( iv) ;
return iv;
}
function getHmacSHA256 ( keyBytes, messageHex ) {
const keyHex = CryptoJS. enc. Hex. parse ( aesjs. utils. hex. fromBytes ( keyBytes) ) ;
const hmac = CryptoJS. HmacSHA256 ( messageHex, keyHex) ;
return hmac. toString ( CryptoJS. enc. Hex) ;
}
function encryptWithMac ( token, plaintext ) {
const key = aaa ( token) ;
const iv = generateRandomIV ( ) ;
const textBytes = aesjs. utils. utf8. toBytes ( plaintext) ;
const padded = aesjs. padding. pkcs7. pad ( textBytes) ;
const aesCbc = new aesjs. ModeOfOperation. cbc ( key, iv) ;
const encryptedBytes = aesCbc. encrypt ( padded) ;
const ivHex = aesjs. utils. hex. fromBytes ( iv) ;
const ciphertextHex = aesjs. utils. hex. fromBytes ( encryptedBytes) ;
const fullDataHex = ivHex + ciphertextHex;
const mac = getHmacSHA256 ( key, fullDataHex) ;
return {
data: fullDataHex,
mac: mac
} ;
}
const token = 'abc123' ;
const msg = 'hello world' ;
const result = encryptWithMac ( token, msg) ;
console. log ( JSON . stringify ( result) ) ;
✅ Node.js 后端验证:backend_node.js
const crypto = require ( 'crypto' ) ;
function deriveKey ( token ) {
return Buffer. from ( token. padEnd ( 16 , '0' ) . slice ( 0 , 16 ) ) ;
}
function verifyEncryptedData ( token, dataHex, macHex ) {
const key = deriveKey ( token) ;
const iv = Buffer. from ( dataHex. slice ( 0 , 32 ) , 'hex' ) ;
const ciphertext = Buffer. from ( dataHex. slice ( 32 ) , 'hex' ) ;
const hmac = crypto. createHmac ( 'sha256' , key) ;
hmac. update ( dataHex) ;
const expectedMac = hmac. digest ( 'hex' ) ;
if ( expectedMac !== macHex) {
throw new Error ( 'MAC 验证失败' ) ;
}
const decipher = crypto. createDecipheriv ( 'aes-128-cbc' , key, iv) ;
decipher. setAutoPadding ( true ) ;
let decrypted = decipher. update ( ciphertext, null , 'utf8' ) ;
decrypted += decipher. final ( 'utf8' ) ;
return decrypted;
}
const token = 'abc123' ;
const { data, mac } = JSON . parse ( '{"data": "...", "mac": "..."}' ) ;
try {
const plaintext = verifyEncryptedData ( token, data, mac) ;
console. log ( '解密成功:' , plaintext) ;
} catch ( e) {
console. error ( e. message) ;
}
✅ Python 后端验证:backend_python.py
from Crypto. Cipher import AES
from Crypto. Hash import HMAC, SHA256
from binascii import unhexlify
def derive_key ( token: str ) - > bytes :
return token. ljust( 16 , '0' ) [ : 16 ] . encode( )
def verify_encrypted_data ( token, data_hex, mac_hex) :
key = derive_key( token)
iv = unhexlify( data_hex[ : 32 ] )
ciphertext = unhexlify( data_hex[ 32 : ] )
h = HMAC. new( key, digestmod= SHA256)
h. update( data_hex. encode( ) )
try :
h. hexverify( mac_hex)
except ValueError:
raise ValueError( 'MAC 验证失败' )
cipher = AES. new( key, AES. MODE_CBC, iv)
padded = cipher. decrypt( ciphertext)
pad_len = padded[ - 1 ]
return padded[ : - pad_len] . decode( )
token = 'abc123'
data = '...'
mac = '...'
try :
print ( '解密成功:' , verify_encrypted_data( token, data, mac) )
except Exception as e:
print ( '失败:' , e)
✅ Nginx + Lua (OpenResty):aes_verify.lua
local aes = require "resty.aes"
local hmac = require "resty.hmac"
local str = require "resty.string"
local cjson = require "cjson"
ngx. req. read_body ( )
local body = ngx. req. get_body_data ( )
local json = cjson. decode ( body)
local data = json. data
local mac = json. mac
local token = ngx. var. http_authorization: sub ( 8 )
local key = token .. string. rep ( "0" , 16 - # token)
key = key: sub ( 1 , 16 )
local hmac_obj = hmac: new ( key, hmac. ALGOS. SHA256)
hmac_obj: update ( data)
local expected_mac = str. to_hex ( hmac_obj: final ( ) )
if expected_mac ~= mac then
ngx. status = 401
ngx. say ( "MAC 验证失败" )
return ngx. exit ( 401 )
end
local iv = str. to_hex ( data: sub ( 1 , 32 ) )
local cipher = data: sub ( 33 )
local aes_cbc = aes: new ( key, nil , aes. cipher ( 128 , "cbc" ) , { iv = iv } )
local decrypted = aes_cbc: decrypt ( str. from_hex ( cipher) )
local pad = string. byte ( decrypted: sub ( - 1 ) )
decrypted = decrypted: sub ( 1 , - pad- 1 )
ngx. say ( "验证通过,原文: " , decrypted)
配置片段:
location /api/secure-data {
content_by_lua_file /etc/nginx/lua/aes_verify.lua;
proxy_pass http://backend_service;
}