Django+Web Crypto API实现RSA密钥验证时解密失败求助
RSA-OAEP解密失败:Django与Web Crypto API交互问题
我在开发Django应用时,实现了一套基于RSA挑战机制的用户私钥验证流程:
- 客户端通过Web Crypto API生成RSA密钥对,将公钥以JWK格式发送给Django后端
- 私钥由用户本地保存为PEM文件
- 预期流程:Django生成UUID并通过公钥加密,发送给客户端;客户端加载本地私钥解密UUID后回传,完成认证
目前各环节都正常,唯独客户端解密时出现异常。
Django后端代码
import secrets import json import base64 from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP from Crypto.Hash import SHA256 # 假设base64url_to_base64是处理URL安全Base64转标准Base64的函数 uuid = secrets.token_hex(16) key = json.loads(request.POST.get('key')) # key是JWK格式 e = int.from_bytes(base64.b64decode(base64url_to_base64(key['e'])), "big") n = int.from_bytes(base64.b64decode(base64url_to_base64(key['n'])), "big") rsakey = RSA.construct((n, e), consistency_check=True) cipher = PKCS1_OAEP.new(rsakey, SHA256) challenge = cipher.encrypt(uuid.encode()) challenge = base64.b64encode(challenge)
客户端JavaScript代码
fileReader.readAsText(file); fileReader.onload = function() { filekey = fileReader.result; filekey = filekey.substring(filekey.indexOf('-----BEGIN PRIVATE KEY-----')); let pem = filekey; const pemHeader = "-----BEGIN PRIVATE KEY-----"; const pemFooter = "-----END PRIVATE KEY-----"; const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length); const binaryDerString = window.atob(pemContents); const binaryDer = str2ab(binaryDerString); // 原处理逻辑 challenge = str2ab(window.atob(challenge.substring(2, challenge.length - 1))); console.log(challenge); window.crypto.subtle.importKey( "pkcs8", binaryDer, { name: "RSA-OAEP", hash: {name: "SHA-256"} }, true, ["decrypt"] ).then((key) => { window.crypto.subtle.decrypt( { name: "RSA-OAEP" }, key, challenge ).then((txt) => { console.log(txt); console.log(ab2str(txt)); }); }); } // 假设str2ab和ab2str是处理字符串与ArrayBuffer互转的工具函数 function str2ab(str) { const buf = new ArrayBuffer(str.length); const bufView = new Uint8Array(buf); for (let i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; } function ab2str(buf) { return String.fromCharCode.apply(null, new Uint8Array(buf)); }
错误信息
客户端调用window.crypto.subtle.decrypt()时抛出异常:
Uncaught (in promise) DOMException: The operation failed for an operation-specific reason
问题排查与解决
最初怀疑是密文格式不兼容(PyCryptodome的PKCS1_OAEP.encrypt()输出字节串,Web Crypto API需要ArrayBuffer),或者Django端通过JWK构造公钥时出错。
经过测试验证,最终发现问题出在Django返回的Base64字符串上:Python的base64.b64encode()返回的是字节对象(如b'abcdef...'),直接返回给客户端后,字符串会带有b''包裹。客户端用atob()处理时,无法识别这些多余字符,导致解密失败。
修正方案
- 后端修正:在Django中将Base64字节对象转为普通字符串:
challenge = base64.b64encode(challenge).decode('utf-8')
- 客户端修正:如果后端没处理,确保客户端拿到纯Base64字符串后再解码,比如去掉
b'前缀和'后缀:
// 确保challenge是纯Base64字符串,没有b''包裹 const cleanChallenge = challenge.replace(/^b'|'$/g, ''); challenge = str2ab(window.atob(cleanChallenge));
修正后,客户端可以正常解密UUID,完成认证流程。
内容的提问来源于stack exchange,提问作者Randusr
相关产品推荐
相关产品推荐

