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 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 : ] ) 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;
}