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站

原文地址:https://www.cnblogs.com/marvey/p/12933632.html