JS与Python非对称加解密报错:密文长度需匹配密钥长度
解决RSA非对称加密流程中的
ValueError: Ciphertext length must be equal to key size错误 问题根源
- 密钥对不匹配:Flask路由每次处理请求都会生成新的密钥对,POST请求解密用的私钥和GET请求传给前端的公钥不是同一组,导致无法解密。
- 填充算法不一致:前端使用OAEP-SHA256加密,后端却用PKCS1_v1_5解密,两者不兼容。
- 密文双重编码错误:JSEncrypt的
encrypt方法已返回Base64编码结果,前端额外调用btoa再次编码,后端未正确解码两次,导致解密时传入的不是正确的加密字节。
修正后的代码
1. Python加密解密函数(统一使用OAEP填充)
from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import padding import base64 def decrypt(private_key, ciphertext_b64): # 解码前端的双重Base64编码 ciphertext = base64.b64decode(base64.b64decode(ciphertext_b64)) private_key_obj = serialization.load_pem_private_key(private_key, password=None) # 使用与前端匹配的OAEP-SHA256解密 decrypted_data = private_key_obj.decrypt( ciphertext, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) return decrypted_data.decode('utf-8') def generate_keypair(): private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() pem_private_key = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) pem_public_key = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) return pem_private_key, pem_public_key
2. Flask路由(保存密钥对到Session,确保前后请求密钥一致)
@app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if session.get('username'): return redirect(url_for('index')) # GET请求生成密钥对并存入Session if request.method == 'GET': private_key, public_key = generate_keypair() session['private_key'] = private_key.decode('utf-8') return render_template('login.html', error_message='Not Authorised', public_key=public_key.decode('utf-8')) if form.validate_on_submit(): username = form.username.data encrpassword = form.password.data # 从Session取出对应私钥 private_key = session.get('private_key').encode('utf-8') try: password = decrypt(private_key=private_key, ciphertext_b64=encrpassword) print(f'username {username} password {password}') access, role = get_user_access_privilege(username) # 后续业务逻辑... except Exception as e: print(f"解密失败: {str(e)}") # 错误处理逻辑
3. JavaScript代码(移除多余的btoa编码)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.3.2/jsencrypt.js"></script> <script> var encryptor = new JSEncrypt(); function encryptAndSubmit(e) { e.preventDefault(); // 阻止默认提交 var password = document.getElementById("password").value; var publicKey = "{{ public_key }}"; encryptor.setPublicKey(publicKey); // JSEncrypt返回的已是Base64密文,无需二次编码 var encryptedPassword = encryptor.encrypt(password, 'sha256'); document.getElementById("password").value = encryptedPassword; document.getElementById("LoginForm").submit(); } document.getElementById("LoginForm").addEventListener("submit", encryptAndSubmit); </script>
额外注意事项
- 确保Flask已配置Session密钥:
app.secret_key = 'your-secure-secret-key',否则无法保存私钥。 - 生产环境可优化密钥生成逻辑,避免每次GET请求生成新密钥,可采用缓存或固定密钥(固定密钥需做好安全防护)。
内容的提问来源于stack exchange,提问作者Prawal Lamshal
相关产品推荐
相关产品推荐

