Skip to content

CryptoJS DES加密解密 #38

@gnipbao

Description

@gnipbao

背景介绍

由于之前业务需求需要对服务器返回的zip包进行解压、解密。 服务器使用的是Java DES加密算法需要前端来进行解密。目前浏览器端解密库里CryptoJS算得上是比较通用的库了。而且CryptoJS加密解密算法很多支持度比较广并且网上资料丰富可以有助与我们快速完成相关业务。下面以DES加密解密为例给大家介绍一下CryptoJS加密解密一些常见问题。

ArrayBuffer、TypeArray和WordArray

  • ArrayBuffer: 用来表示一个通用的、固定长度的二进制数据缓冲区。你不能直接操纵一个ArrayBuffer中的内容;你需要创建一个类型化数组的视图或一个描述缓冲数据格式的DataView,使用它们来读写缓冲区中的内容。
  • TypeArray:用来生成内存的视图,通过9个构造函数,可以生成9种数据格式的视图,比如Uint8Array(无符号8位整数)数组视图, Int16Array(16位整数)数组视图, Float32Array(32位浮点数)数组视图等等
  • WordArray: 这个在JavaScript中没有任何描述是CryptoJS实现的一种数据结构。CryptoJS中大量使用的数据结构,是一个代表32位无符号数组对象,当你传入字符串时CryptoJS会自动包装成utf8编码的WordArray。可以使用CryptoJS.enc.Utf8工具来转换。

总结: ArrayBuffer对象代表原始的二进制数据,TypedArray对象代表确定类型的二进制数据,DataView对象代表不确定类型的二进制数据。它们支持的数据类型一共有9种。WordArray对象是CryptoJS内部实现的一种二进制数据结构用32位数组表示。具体实现感兴趣可以参考WordArray源码

简单字符串DES加解密

对于字符串加解密比较简单我们可以直接看官方API搜DES加密即可下面给出封装代码

// encrypt string
function encryptByDES(message, key) {
  let keyHex = CryptoJS.enc.Utf8.parse(key);
  let encrypted = CryptoJS.DES.encrypt(message, keyHex, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  });
  return encrypted.toString();
}
// decrypt string
function decryptByDES(ciphertext, key) {
  let keyHex = CryptoJS.enc.Utf8.parse(key);
  // direct decrypt ciphertext
  let decrypted = CryptoJS.DES.decrypt(
    {
      ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
    },
    keyHex,
    {
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7
    }
  );
  return decrypted.toString(CryptoJS.enc.Utf8);
}

ArrayBuffer DES加解密

考虑到平时项目中大部分是加载一个具体的文件流进行加解密这种场景比较多所以重点讲一下。这部分官方是没有给出说明需要自己摸索。

WordArray和uInt8Array转换

由于CryptoJS中大量使用WordArray所以在加密解密文件流的时候需要和uInt8Array之间进行转换 不然解码出来可能是乱码。
下面封装的工具可以很方便提供转换,可以在引入CryptoJS时候对CryptoJS.enc做一个增强

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

DES加密

// encrypt arraybuffer
let _encrypt = (key, u8array) => {
  let keyHex = CryptoJS.enc.Utf8.parse(key);
  let encryptedWordArray = CryptoJS.enc.u8array.parse(u8array);
  let encrypted = CryptoJS.DES.encrypt(
    encryptedWordArray
    keyHex,
    {
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7
    }
  );
  return encrypted;
};

DES解密

// decrypt arraybuffer from file
let _decrypt = (u8array, key) => {
  let keyHex = CryptoJS.enc.Utf8.parse(key);
  let decryptedWordArray = CryptoJS.enc.u8array.parse(u8array);
  let decrypted = CryptoJS.DES.decrypt(
    decryptedWordArray.toString(CryptoJS.enc.Base64),
    keyHex,
    {
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7
    }
  );
  return decrypted.toString(CryptoJS.enc.Utf8);
};

在线演示

codesandbox
建议copy代码在本地运行 由于codesandbox地址saveAs会有问题。

注意点

  • 服务端Java加密浏览器端解密
    对于这种情况一点要保证CryptoJS使用的的模式modepadding方式和Java的一致,例如ECB mode和PKCS5Padding。由于CryptoJS只有Pkcs7 经测试Pkcs7可以兼容PKCS5Padding。具体对于mode padding感兴趣的同学可以自行google。
  • 报错_Error: Malformed UTF-8 data_
    这个错误出现说明你解密后的数据可能出现乱码或者解密密钥错误解出来的数据不对等等情况比较多。总之就是没解对就是了,这个时候需要自己检测下流程上有没有问题,密钥是否正确。也可以和服务端加密同学讨论一下他们加密流程 看是不是先进行base64加密后使用DES加密。
  • WordArray
    由于我们加解密一般都是以文件的形式进行,所以加密时候需要把Uint8Array二进制数据转成WordArray传进来加密 ,解密的时候需要把传进来的Uint8Array转成WordArray再转成base64字符串来解密。

参考资料

https://cryptojs.gitbook.io/docs/
https://stuk.github.io/jszip/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
http://www.appblog.cn/2019/07/01/CryptoJS%E4%B8%ADWordArray/

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions