1.图片加解密的公共数据:key、iv等

// 字符串转字节数组的方法
const stringToBytes = (str: string) => {
  let ch = 0
  let st = []
  let re: any[] = []
  for (let i = 0; i < str.length; i++) {
    ch = str.charCodeAt(i) // get char
    st = [] // set up "stack"
    do {
      st.push(ch & 0xFF) // push byte to stack
      ch = ch >> 8 // shift value down by 1 byte
    }
    while (ch)
    // add stack contents to result
    // done because chars have "wrong" endianness
    re = re.concat(st.reverse())
  }
  // return an array of bytes
  return re
}

/**
 * 生成加解密:公共加密key、iv、
 */
export const CryptoGlobal = (Codekey: string) => {
  // 将上面下载的图片转为base64编码,并处理key和iv的格式
 
  const key = Codekey || 'xxxxxxxxxx'
  const iv = 'xxxxxxxxxxxxxxx'

  // 密钥转字节数组(16位)  
  let keyBy = stringToBytes(key)
  let ivBy = stringToBytes(iv)

  
  // 字节数组转Uint8Array
  let keyBv = new Uint8Array(keyBy)
  let ivBv = new Uint8Array(ivBy)
  
  // Uint8Array转WordArray
  let keyWA = CryptoU8array.u8array.parse(keyBv) 
  let ivWA = CryptoU8array.u8array.parse(ivBv)
  return {
    key: keyWA,
    subtile: {
      iv: ivWA,
       // 解密方式和后端商定为为CBC模式
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    }
  }
}

2.图片插件所需的方法u8array方法

注:此代码我用了单独的代码管理,文件名:enc-u8array.ts
const CryptoJS = require(‘crypto-js’);

CryptoJS.enc.u8array = {
  /**
   * Converts a word array to a Uint8Array.
   *
   * @param {WordArray} wordArray The word array.
   *
   * @return {Uint8Array} The Uint8Array.
   *
   * @static
   *
   * @example
   *
   * var u8arr = CryptoJS.enc.u8array.stringify(wordArray);
   */
  stringify: function (wordArray: { words: any; sigBytes: any }) {
    // Shortcuts
    var words = wordArray.words
    var sigBytes = wordArray.sigBytes
    // Convert
    var u8 = new Uint8Array(sigBytes)
    for (var i = 0; i < sigBytes; i++) {
      var byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff
      u8[i] = byte
    }
    return u8
  },
  /**
   * Converts a Uint8Array to a word array.
   *
   * @param {string} u8Str The Uint8Array.
   *
   * @return {WordArray} The word array.
   *
   * @static
   *
   * @example
   *
   * var wordArray = CryptoJS.enc.u8array.parse(u8arr);
   */
  parse: function (u8arr: string | any[]) {
    // Shortcut
    var len = u8arr.length
    // Convert
    var words: number[] = []
    for (var i = 0; i < len; i++) {
      words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8)
    }
    return CryptoJS.lib.WordArray.create(words, len)
  }
}
export default {
  u8array: CryptoJS.enc.u8array
}

3.图片加密

const CryptoJS = require('crypto-js');
import CryptoU8array from './enc-u8array'
import { CryptoGlobal } from './cryptoGlobal'
/**
 * 图片加密
 */
export function avatarEncrypt(type: string, reorganizationUrl: string, file: any, Codekey?: string, ) {
  const recodekey = Codekey?.slice(0, 16)
  const { key, subtile } = CryptoGlobal(recodekey)
  const messageWordArray = CryptoU8array.u8array.parse(file);

  let encrypted = CryptoJS.AES.encrypt(messageWordArray, key, subtile);
  const encryptedBytes = encrypted.ciphertext;
  const toWordArray = CryptoU8array.u8array.stringify(encryptedBytes)
  let blob = new Blob([toWordArray], { type }); // 将加密后串转blob
  let blobToFile = new File([blob], reorganizationUrl);
  return blobToFile
}

4.图片解密

import axios from 'axios'
const CryptoJS = require('crypto-js');
import CryptoU8array from './enc-u8array'
import { CryptoGlobal } from './cryptoGlobal'

/**
 * base64转blob
 * @param {*} base64 
 * @returns 
 */
function base64ToBlob(base64: string) {
  const binary = atob(base64.split(',')[1]);
  const array = [];
  for (let i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  return new Blob([new Uint8Array(array)], { type: 'image/jpeg' }); // 这里的type可以根据实际情况进行修改
}

/**
 * 头像解密
 * @param {*} buffer 
 * @returns 
 */
function avatarDecrypt(buffer: ArrayBuffer, Codekey?: string) {
  let view = new Uint8Array(buffer)

  // 将Uint8Array 转成 WordArray
  let contentWA = CryptoU8array.u8array.parse(view)
  // base64字符串
  let dcBase64String = contentWA.toString(CryptoJS.enc.Base64)
  // 解密
  const { key, subtile } = CryptoGlobal(Codekey)
  let decryptedData = CryptoJS.AES.decrypt(dcBase64String, key, subtile)
  console.log(decryptedData)
  
  // 把解密后的对象再转为base64编码,这步是关键,跟解密文字不同
  let d64 = decryptedData.toString(CryptoJS.enc.Base64)
  let encodedData = 'data:image/png;base64,' + d64

  // 将blob解析URL
  const blob = base64ToBlob(encodedData);
  var url = URL.createObjectURL(blob);
  return url
}

/**
 * 获取图片路径并调用头像解密方法
 * @param list 路径
 * @param key sessionId 对话图片才会需要用
 * @returns 
 */
export async function decodeAvatarUrl(list: string, key?: string) {
  const resetList = list[0]=== '{' && list[list.length - 1] === '}' ? JSON.parse(list) : list
  let isListType = null;

  if(resetList != null) {
    
    isListType = typeof resetList === 'string' ? resetList : resetList?.url
    isListType = isListType.replace(/^http:/gi, '')

    // 判断是否需要解密头像, 并判断图片数据是否有损坏,损坏显示默认头像
    try {
      
      if(isListType.includes('@#¥%&')) {
        const res: {data: ArrayBuffer} = await axios.request({
          responseType: 'arraybuffer',
          url: isListType,
          method: 'get'
        })
        if(res.data) {
          const reKey = key?.slice(0, 16)
          const avatarBase64 = avatarDecrypt(res.data, reKey)
          isListType = avatarBase64
        } else {
          isListType = null
        }
      }
      
    } catch (error) {
      isListType = null
    }
  }
  return isListType
}

5.图片加密使用

/** 上传图片方法传参校验 */
type UploadFileType = {
  /** 图片数据:Uint8Array */
  stream: any;
  /** 图片名字 */
  key: string,
  /** 上传图片的类型: 1:头像 2:消息图片 */
  updateType: number;
  /** sessionId */
  sessionId: string
}
/**
 * 上传图片 -> 七牛云
 *
 * @param stream           图片流:Uint8Array
 * @param stream           图片名称
 * @param updateType       1:头像 2:消息图片
 * @param sessionId       1:头像 2:消息图片
 * 
 */
export const uploadFile = ({ stream, key, updateType, sessionId }: UploadFileType) => {
  return new Promise(async (resolve: (res: { rescode: number, url: string, error: any }) => void, reject) => {
    
    const folderOs = 'Desktop'
    const folder = updateType === 1 ? 'avatars' : 'conversations'
    const folderEnd = '@#¥%&'
    const reorganizationUrl = `${folderOs}/${folder}/${reorganizationName()}${folderEnd}`

    const imageType = 'image/' + key.split('.').pop()
    const putExtra = { // 上传需要的参数
        fname: '',
        params: {},
        mimeType: [imageType]
    };
    const config = { // 上传需要的参数
        useCdnDomain: true,
    };
    // token是接口获取
    const { data } = await updateImageToken()
    const { qiniuLinkUrl, token } = data
    const decode: any = await avatarEncrypt(imageType, reorganizationUrl, stream, sessionId)
    const observable = qiniu.upload(decode, reorganizationUrl, token, putExtra, config);
    observable.subscribe({
      next(res: any){
        // console.log(res)
      },
      error(err: any){ // 失败
        // console.log(err)
        reject({
          error: err,
          url: '',
          rescode: 100
        })
      }, 
      complete(res: any){ // 成功
        // console.log(res)
        resolve({
          error: null,
          url: qiniuLinkUrl+reorganizationUrl,
          rescode: 200
        })
      }
    })
  })
}

6.图片解密使用

import { decodeAvatarUrl } from '@/utils/decodeAvatar'
// src: 图片路径,sessionId: 唯一key值,每条对话单独的id
decodeAvatarUrl(src, sessionId)

参考:
CSDN(注:仅能参考解密,不够全面):https://blog.csdn.net/ningtt/article/details/118301703
简书(参考的这篇加密,加解密都有,较为全面):https://www.jianshu.com/p/1f75cd571786

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部