CryptoJS - 解密加密文件

CryptoJS - Decrypt an encrypted file

我正在尝试编写一个应用程序,在浏览器中使用 JS 对文件进行端到端加密。但是我似乎无法正确解密所有文件。

TL;DR 由于整体加密大于 1MB 的文件是不切实际的,所以我尝试逐块加密它们。这样做之后,我尝试将加密的单词(由 CryptoJS 的 WordArray 产生)写入一个 blob。至于解密,我读取文件并根据加密块时生成的映射将它们拆分为块并尝试解密它们。问题是解密结果是0位!

我想我在正确解密时没有读取块。请查看下面的函数代码 getBlob(将数据写入 blob)和 decryptFile 的最后一部分用于读取块。

更多解释

我使用默认设置的 CryptoJS AES。

现在我的代码如下所示:

function encryptFile (file, options, resolve, reject) {
  if (!options.encrypt) {
    return resolve(file)
  }
  if (!options.processor || !options.context) {
    return reject('No encryption method.')
  }

  function encryptBlob (file, optStart, optEnd) {
    const start = optStart || 0
    let stop = optEnd || CHUNK_SIZE
    if (stop > file.size - 1) {
      stop = file.size
    }

    const blob = file.slice(start, stop)
    const fileReader = new FileReader()

    fileReader.onloadend = function () {
      if (this.readyState !== FileReader.DONE) return

      const index = Math.ceil(optStart / CHUNK_SIZE)
      const result = CryptoJS.lib.WordArray.create(this.result)
      encryptedFile[index] = encrypt(result)

      chunksResolved++
      if (chunksResolved === count) {
        const {sigBytes, sigBytesMap, words} = getCipherInfo(encryptedFile)
        const blob = getBlob(sigBytes, words)

        resolve(blob, Object.keys(sigBytesMap))
      }
    }
    fileReader.readAsArrayBuffer(blob)
  }

  let chunksResolved = 0
  const encryptedFile = []
  const CHUNK_SIZE = 1024*1024
  const count = Math.ceil(file.size / CHUNK_SIZE)
  const encrypt = value => options.processor.call(
    options.context, value, 'file',
    (v, k) => CryptoJS.AES.encrypt(v, k))

  for (let start = 0; (start + CHUNK_SIZE) / CHUNK_SIZE <= count; start+= CHUNK_SIZE) {
    encryptBlob(file, start, start + CHUNK_SIZE - 1)
  }
}

如您所见,我正在尝试逐块读取文件(每个块为 1MB 或 fileSize % 1MB)作为 ArrayBuffer,将其转换为 WordArray 以便 CryptoJS 理解和加密它。

加密所有块后,我尝试将它们必须的每个单词写入一个 blob(使用我在 Google 代码中的 CryptoJS 问题中找到的代码,如下所述)和 我猜这里是哪里出了问题。我还为加密块的结束位置生成了一个映射,以便稍后可以使用它从二进制文件中获取块以进行解密。

下面是我解密文件的方法:

function decryptFile (file, sigBytesMap, filename, options, resolve, reject) {
  if (!options.decrypt) {
    return resolve(file)
  }
  if (!options.processor || !options.context) {
    return reject('No decryption method.')
  }

  function decryptBlob (file, index, start, stop) {
    const blob = file.slice(start, stop)
    const fileReader = new FileReader()

    fileReader.onloadend = function () {
      if (this.readyState !== FileReader.DONE) return

      const result = CryptoJS.lib.WordArray.create(this.result)
      decryptedFile[index] = decrypt(result)

      chunksResolved++
      if (chunksResolved === count) {
        const {sigBytes, words} = getCipherInfo(decryptedFile)
        const finalFile = getBlob(sigBytes, words)

        resolve(finalFile, filename)
      }
    }
    fileReader.readAsArrayBuffer(blob)
  }

  let chunksResolved = 0
  const count = sigBytesMap.length
  const decryptedFile = []
  const decrypt = value => options.processor.call(
    options.context, value, 'file',
    (v, k) => CryptoJS.AES.decrypt(v, k))

  for (let i = 0; i < count; i++) {
    decryptBlob(file, i, parseInt(sigBytesMap[i - 1]) || 0, parseInt(sigBytesMap[i]) - 1)
  }
}

解密与加密完全一样,但不起作用。虽然块不再是 1MB,但它们仅限于地图中提到的 sigBytes。解密无结果! sigBytes: 0.

下面是生成 blob 和获取 sigbytesMap 的代码:

function getCipherInfo (ciphers) {
  const sigBytesMap = []
  const sigBytes = ciphers.reduce((tmp, cipher) => {
    tmp += cipher.sigBytes || cipher.ciphertext.sigBytes
    sigBytesMap.push(tmp)
    return tmp
  }, 0)

  const words = ciphers.reduce((tmp, cipher) => {
    return tmp.concat(cipher.words || cipher.ciphertext.words)
  }, [])

  return {sigBytes, sigBytesMap, words}
}

function getBlob (sigBytes, words) {
  const bytes = new Uint8Array(sigBytes)
  for (var i = 0; i < sigBytes; i++) {
    const byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff
    bytes[i] = byte
  }

  return new Blob([ new Uint8Array(bytes) ])
}

我猜问题出在我用来读取加密块的方法上。或者写他们!

我还应该提一下,之前我在加密方面做了一些不同的事情。我使用默认编码(我认为是 CryptoJS.enc.Hex)的 toString 方法将每个 WordArray 作为 CryptoJS.AES.encrypt 的结果进行字符串化,但有些文件没有正确解密。它与原始文件的大小无关,而与它们的类型无关。同样,我在猜测!

原来问题是 CryptoJS.AES.decrypt(value, key) 返回的 WordArray 有 4 个额外的单词作为填充,不应包含在最终结果中。 CryptoJS 尝试取消填充结果,但仅相应地更改 sigBytes 而不会更改 words。所以在解密时,在将块写入文件之前弹出那些额外的单词。 4 个单词用于完整的块,3 个用于较小的块(最后一个块)。

check this issue

import CryptoJS from "crypto-js";

async function encryptBlobToBlob(blob: Blob, secret: string): Promise<Blob> {
    const wordArray = CryptoJS.lib.WordArray.create(await blob.arrayBuffer());
    const result = CryptoJS.AES.encrypt(wordArray, secret);
    return new Blob([result.toString()]);
}
export async function decryptBlobToBlob(blob: Blob, secret: string): Promise<Blob> {
    const decryptedRaw = CryptoJS.AES.decrypt(await blob.text(), secret);
    return new Blob([wordArrayToByteArray(decryptedRaw)]);
}

function wordToByteArray(word, length) {
    const ba = [];
    const xFF = 0xff;
    if (length > 0) ba.push(word >>> 24);
    if (length > 1) ba.push((word >>> 16) & xFF);
    if (length > 2) ba.push((word >>> 8) & xFF);
    if (length > 3) ba.push(word & xFF);

    return ba;
}

function wordArrayToByteArray({ words, sigBytes }: { sigBytes: number; words: number[] }) {
    const result = [];
    let bytes;
    let i = 0;
    while (sigBytes > 0) {
        bytes = wordToByteArray(words[i], Math.min(4, sigBytes));
        sigBytes -= bytes.length;
        result.push(bytes);
        i++;
    }
    return new Uint8Array(result.flat());
}

async function main() {
    const secret = "bbbb";
    const blob = new Blob(["1".repeat(1e3)]);
    const encryptedBlob = await encryptBlobToBlob(blob, secret);
    console.log("enrypted blob size", encryptedBlob.size);
    const decryptedBlob = await decryptBlobToBlob(encryptedBlob, secret);
    console.log("decryptedBlob", decryptedBlob);
    console.log(await decryptedBlob.text());
}
main();