利用 RSA 来加密传输 AES的密钥,用 AES的密钥 来加密数据 RSA的公钥,私钥由后端提供
1创建crpto.js文件 2// 引入crypto-js,封装 AES加密,解密方法 3import CryptoJS from 'crypto-js' 4 5/** 6word :要加密的数据, 7key:秘钥(随机生成) 8*/ 9 10// AES加密 11encryptAES (word, key) { 12 let keyAES = CryptoJS.enc.Utf8.parse(key) 13 let srcs = CryptoJS.enc.Utf8.parse(word) 14 let enc = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}) // 加密模式为ECB,补码方式为PKCS7 15 return enc.toString() 16}, 17// AES解密 18decryptAES (word, key) { 19 let key = CryptoJS.enc.Utf8.parse(key) 20 let dec = CryptoJS.AES.decrypt(word, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}) 21 return CryptoJS.enc.Utf8.stringify(dec).toString() 22}
后端每次接收到请求,都会将该请求的nonce存入缓存并保持60秒(根据需要设置),时间过后该值将被移除,建议后端采用Redis存储nonce,这样可省去检测和移除nonce的代码。如果后端发现当前请求的nonce存在于已存储的nonce之中,则此请求发生重复 如果只使用nonce我们只能保证该请求60秒内不会重复,但60秒后依然任人宰割,这不是要的结果。所以timestamp将用来限制时间,后端时间戳减去前端发送请求的时间戳,得到的差值为N秒,如果N秒大于60秒则此请求过期,那么则可以保证,60秒内因为nonce相同而被判为请求重放,60秒后因为时间差超过而被判为请求已过期,因此确保了请求不会被重放。
1创建RSA.js文件 2import CryptoJS from 'crypto-js' 3import {JSEncrypt} from 'jsencrypt' 4 5// data:需要加密的请求数据,这里暂时使用假数据 6rsaEncrypt(data){ 7 let data = { name:"张三",telPhone:"15789087655" } 8 // 获取时间戳 9 let timestamp = new Date().getTime(); 10 // 获取随机码 11 let nonce = randomCode(); 12 data.timestamp = timestamp; 13 data.nonce = nonce; 14 // 字段排序(按照首字母) 15 const keys = Object.keys(data); 16 keys.sort(); 17 // 拼接字符串 18 let signStr = ""; 19 for(let i=0;i<keys.length;i++){ 20 const element = keys[i]; 21 singStr+=`${element}=${data[element]}&`; 22 } 23 // 拼接key(登录时后端返回的key) 24 let key = "SnksifhLAteurhf" // 这里用的假的 25 signStr = signStr + 'key='+key; 26 // 计算MD5哈希值 27 // 拼接的字符串很容易被攻击者伪造签名并篡改数据,所以需要加上登录时返回的唯一的key 28 // MD5的目的并不是为了隐藏明文数据,而是让后端进行数据校验 29 // 后端取得请求数据后,将除了sign之外的参数名进行字典排序, 30 // sign用一个临时变量存下,然后排好序的参数和前端一样拼接得到字符串。 31 // 接下来进行MD5计算即可得到后端获得的签名,此时与请求中 32 // 携带的sign比较是否一致则可确定签名是否有效,如果不一致返回签名错误 33 const sign = CryptoJS.MD5(signStr).toString(); 34 data.sign = sign; 35 36 // RSA公钥(登录时后端返回的)(这里用的假的,真实的公钥很长------) 37 let publicKey = "Misfsdfsfffsfsdfsfss" 38 // 随机生成AES秘钥(长度自定义) 39 let aesRandomStr = randomCode(); 40 // 使用AES秘钥加密数据 41 let datas = crpto.encryptAES(JSON.Stringfy(data),aesRandomStr) 42 // rsa加密AES秘钥 43 const encrypts = new JSEncrypt() 44 // 设置公钥 45 encrypts.setPublicKey(publicKey) 46 // 加密AES秘钥 47 let encryptedKey = encrypts.encrypt(aesRandomStr) 48 let json={ 49 "encryptedData":data, 50 "encryptedKey":encryptedKey 51 } 52 return json 53} 54 55 // 生成随机码 56 randomCode(){ 57 let possible="QWERTYUIOPLKMJNHBGVFCDXSZAqwertyuiolpkmjnhbgvfcdxsza1234567890" 58 let randomCode = ''; 59 for(let i=0;i<16;i++){ 60 randomCode+=possible.charAt(Math.floor(Math.random()*possible.length)) 61 } 62 63 // 解密 64 // 解密是后端进行的,这里的解密方法只是为了前端自测,确保前端加密的数据-自己能解开 65 rsaDecryptData(result){ 66 const decrypted = new JSEncrypt() 67 let privateKey = "hasahdaiasuivuivb"; 68 decrypted.setPrivateKey(privateKey) 69 let aesKey = decrypted.decrypt(result.encryptedKey) 70 let dataRes = crpto.decryptAES(result.encryptedData,aesKey) 71 return JSON.Parse(dataRes) 72 } 73}
