> 技术文档 > 使用AES-CBC + HMAC-SHA256实现前后端请求安全验证_aes cbc256

使用AES-CBC + HMAC-SHA256实现前后端请求安全验证_aes cbc256

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

// === frontend.js ===// 前端:使用 AES-CBC 加密 + HMAC-SHA256 签名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

// backend_node.jsconst 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\'); // 验证 HMAC 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

# backend_python.pyfrom Crypto.Cipher import AESfrom Crypto.Hash import HMAC, SHA256from binascii import unhexlifydef 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:]) # 验证 HMAC 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 = \'...\' # 前端 datamac = \'...\' # 前端 mactry: print(\'解密成功:\', verify_encrypted_data(token, data, mac))except Exception as e: print(\'失败:\', e)

✅ Nginx + Lua (OpenResty):aes_verify.lua

-- aes_verify.lualocal 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.datalocal mac = json.maclocal 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)endlocal 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;}