使用CryptoJS在JS加密消息,FastAPI(Python)解密遇填充错误
AES跨语言(JS-Python)解密填充错误解决方案
问题场景
使用CryptoJS在前端加密消息,通过POST请求发送到FastAPI后端解密时,出现ValueError: Padding is incorrect.错误,核心代码如下:
前端JS代码(原错误版本)
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script> var message = 'Message'; var key = '1234567812345678'; var encryptedAES = CryptoJS.AES.encrypt(message, key, {mode: CryptoJS.mode.ECB}); var url2 = new URL('http://localhost:8000/decrypt/'); $("#encrypt").on("click", evt => { fetch(url2, { method: 'POST', body: encryptedAES }).then().catch((error) => { console.log(error.message); }); }); </script>
后端Python代码(原版本)
import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from fastapi import FastAPI, Request app = FastAPI() @app.post('/decrypt/') async def decrypt(request: Request): key = '1234567812345678' data = await request.body() new_data = base64.b64decode(data) cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB) final = unpad(cipher.decrypt(new_data), 16) return final
核心问题及解决办法
1. 前端JS端的关键修正
(1)正确传递密钥
CryptoJS直接传入字符串密钥时,会默认用PBKDF2算法推导密钥,而非直接使用字符串字节内容,导致前后端密钥不一致。需将字符串密钥转为WordArray:
var key = CryptoJS.enc.Utf8.parse('1234567812345678');
(2)正确获取加密后的密文
CryptoJS.AES.encrypt()返回的是CipherParams对象,直接作为请求体发送会被序列化为[object Object],导致后端解码出错误密文。需调用toString()获取base64格式密文:
var encryptedAES = CryptoJS.AES.encrypt(message, key, {mode: CryptoJS.mode.ECB}).toString();
修正后的完整JS代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script> var message = 'Message'; var key = CryptoJS.enc.Utf8.parse('1234567812345678'); var encryptedAES = CryptoJS.AES.encrypt(message, key, {mode: CryptoJS.mode.ECB}).toString(); var url2 = new URL('http://localhost:8000/decrypt/'); $("#encrypt").on("click", evt => { fetch(url2, { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: encryptedAES }).then(response => response.json()) .then(data => console.log(data)) .catch((error) => { console.log(error.message); }); }); </script>
2. 后端Python端的补充调整
(1)增加密文长度校验
AES密文长度必须是16的倍数,解码后可增加合法性校验:
new_data = base64.b64decode(data) if len(new_data) % 16 != 0: return {"error": "Invalid ciphertext length"}
(2)返回可读字符串
原代码返回字节数组,需转为UTF-8字符串:
return final.decode('utf-8')
修正后的完整Python代码
import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from fastapi import FastAPI, Request app = FastAPI() @app.post('/decrypt/') async def decrypt(request: Request): key = '1234567812345678' data = await request.body() try: new_data = base64.b64decode(data.strip()) if len(new_data) % 16 != 0: return {"error": "Invalid ciphertext length"} cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB) final = unpad(cipher.decrypt(new_data), AES.block_size) return final.decode('utf-8') except Exception as e: return {"error": str(e)}
3. 其他注意事项
- ECB模式安全性:ECB模式不提供语义安全性,生产环境建议使用CBC/GCM等带IV的模式,GCM还支持自动校验完整性
- 填充方式一致性:CryptoJS默认使用PKCS#7填充,Python的
unpad函数默认也是PKCS#7,无需额外配置;若手动指定填充,需确保前后端一致 - 请求头设置:发送纯文本密文时,建议设置
Content-Type: text/plain,避免后端解析错误
内容的提问来源于stack exchange,提问作者Tommy Harris
相关产品推荐
相关产品推荐

