crypto-js前端实现加密学习日志(—项目实践)
1、由于项目需要,对文件进行加密,然后上传至阿里oss。出于后端带宽压力,在前端进行加密。由于加密过程比较耗时,容易阻塞主进程,所以决定使用worker来进行。
废话不多说,直接上代码。
2、首先是utils.ts,主要是封装一些加密、解密、通用工具类。
import CryptoJs, {WordArray, AES} from 'crypto-js'; /** * 加密函数使用的CryptoJs的AES/CBC/pkcs7进行加密 * @param {*} key 加密用的秘钥,由于项目中的key使用了base64编码,所以需要解码 * @param {*} content 要加密的内容,CryptoJs接收WordArray | string * @returns res返回值为加密后的WordArray, 大端序。 */ export const encode = (key: string, content: string | WordArray) => { const encodeKey = CryptoJs.enc.Base64.parse(key) const ivWords = [...encodeKey.words].splice(0, 4) const iv = CryptoJs.lib.WordArray.create(ivWords) const res = AES.encrypt(content, encodeKey, { iv: iv, mode: CryptoJs.mode.CBC, padding: CryptoJs.pad.Pkcs7 }).ciphertext return res } /** * * @param {*} array 要初始化为WordArray的typedArray * @returns res返回值为WordArray(Crypto使用的WordArray) */ export type typedArray = String | ArrayBuffer | Int32Array | Uint32Array | Int16Array | Uint16Array | Int8Array | Uint8ClampedArray | Uint8Array | Float32Array | Float64Array export const createWordArray = (array: typedArray) => { return CryptoJs.lib.WordArray.create(array) } /** * @param {*} array 要解密的WordArray * @returns res返回值为CipherParams(CryptoJS解密用的) */ export const formatCipher = (array: WordArray) => { return CryptoJs.lib.CipherParams.create({ciphertext: array}) } /** * 解密函数 * @param {*} key //解密时候用的key * @param {*} content // 要解密的内容(CryptoJS接收string | WordArray) * @returns res // 解密后的文件内容,通过base64编码 */ export const decode = (key: string, content: string | WordArray) => { const decodeKey = CryptoJs.enc.Base64.parse(key) const ivWords = [...decodeKey.words].splice(0, 4) const iv = CryptoJs.lib.WordArray.create(ivWords) var res = AES.decrypt(content, decodeKey, { iv: iv, mode: CryptoJs.mode.CBC, padding: CryptoJs.pad.Pkcs7 }).toString(CryptoJs.enc.Base64) return res } /** * WordArray 转化为DataView * @param wordArray * @returns DataView */ export const wAToB = (wordArray: WordArray) => { const buffer = new ArrayBuffer(wordArray.sigBytes) let resView = new DataView(buffer) for(let i = 0; i < wordArray.words.length; i++) { resView.setInt32(i * 4, wordArray.words[i]) } return resView }
2.encode.worker.ts这是加密的worker线程,
import {encode, createWordArray, wAToB} from '../utils/utils'; const enWorker: Worker = self as any; enWorker.onmessage = function(e) { const {file, tokenInfo} = e.data const reader = new FileReader(); reader.onload = function() { const { key, fileName } = tokenInfo; let content = reader.result; content = createWordArray(content); content = encode(key, content); const res = wAToB(content) const resFile = new File([res], fileName, {type: file.type}) enWorker.postMessage({content: resFile, done: true}) } reader.readAsArrayBuffer(file) } export default null as any
3、decode.worker.ts解密的worker线程
import {decode, formatCipher, createWordArray} from '../utils/utils'; const deWorker: Worker = self as any; deWorker.onmessage = function(e) { const {file, key, type} = e.data let content = createWordArray(file) content = formatCipher(content) const res = decode(key, content) const resStr = `data:${type};base64,${res}` deWorker.postMessage({content: resStr, done: true}) } export default null as any
4、创建index.tsx。把worker.ts引入,然后实例化。
import React, {useState} from 'react'; import './index.css'; import EncodeWorker from '../workers/encode.worker'; import DecodeWorker from '../workers/decode.worker'; function EncodePage() { const [fileList, setFileList] = useState([]) const changeFile = (files: FileList) => { console.log(files) const file = files[0] if(!file) { return } if(file.size > 1024*1024*50) { return alert('文件太大') } changeFileList({ id: `-${file.lastModified}`, name: file.name, content: null }) encodeFile(file) } const changeFileList = (file) => { const ind = fileList.findIndex( item => item.id === file.id ) if(ind === -1) { fileList.push(file) } else { fileList.splice(ind, 1, file) } setFileList([...fileList]) } // 加密 const encodeFile = (file: File) => { const encodeWorker = new EncodeWorker(); encodeWorker.postMessage({file, tokenInfo: {fileName: file.name, key: 'axyIIVwxqRnPnqc9RDRzXg=='}}) encodeWorker.onmessage = function(e) { let {content, done} = e.data; if(done){ encodeWorker.terminate() changeFileList({ id: `-${file.lastModified}`, name: file.name, content: content }) console.log('end-encode', new Date().getTime()) } } } //解密 const decodeFile = (file: String | ArrayBuffer, name: String, type: String) => { const decodeWorker = new DecodeWorker(); decodeWorker.postMessage({file, key: 'axyIIVwxqRnPnqc9RDRzXg==', type}) decodeWorker.onmessage = function(e) { let {content, done} = e.data; if(done){ decodeWorker.terminate() console.log('end-decode', new Date().getTime()) downloadFile(content, name) } else { console.log(content) } } } const downloadFile = (url, name) => { let a = document.createElement('a') a.href = `${url}` a.target = "__blank" a.download = name || "" a.click() } const downEncodeFile = (file) => { // console.log(file) const reader = new FileReader() reader.onload = function() { // console.log(reader.result) downloadFile(reader.result, file.name) } reader.readAsDataURL(file) } const downDecodeFile = (file) => { const reader = new FileReader() reader.onload = function() { decodeFile(reader.result, file.name, file.type) } reader.readAsArrayBuffer(file) } return ( <div className="upload-file"> <div className="file-btn"> <span>选择文件</span> <input type="file" onChange={(e) => changeFile(e.target.files)} /> </div> <div className="file-list"> { fileList.map((v) => { return ( <div key={v.id} className="file-item"> {v.name} ({v.content?'加密完成':'加密中...'}) <span onClick={() => downEncodeFile(v.content)} className="download-btn">下载加密文件</span> <span onClick={() => downDecodeFile(v.content)} className="decode-btn">下载解密文件</span> </div> ) }) } </div> </div> ) } export default EncodePage
5、在线测试demo请移动我的github站。