JavaScript使用AES加密文件

8,998 阅读3分钟

JavaScript 使用Crypto.js 进行AES加密文件

最近刚好遇见需要将文件加密上传的需求,自己也在这里踩了很多坑,被卡了很久,网上也很少有详细介绍对文件进行AES加密的文章,一般都只是对简单的字符串进行加密的,今天就写一篇文章记录一下在这里遇到的问题和最后的解决方案。

Crypto.js库的介绍

Crypto.js库是使用比较广泛的一个加密库,提供了MD5、Hmac、AES、RSA等加密算法,用法也比较简单,所以选用的是这个库来进行加密操作。
运行下面的代码进行Crypto的安装

npm install crypto-js

Crypto.js 加密和解密

这里简单介绍一下AES加密算法,AES加密时需要传入Key和Iv,其中Key的长度分为128、192和256位,使用不同长度的Key进行加密,所得到的密文也是完全不同的。AES加密模式分为CBC、ECB、CTR、OCF、CFB这五种,填充模式有NoPadding、PKCS#7、PKCS#5、ZerosPadding、ISO 10126等,选择不同的加密和填充模式,得到的密文也是不同的。这里就不再对AES加密算法详细的展开介绍了,如果想更详细的了解,可以去看看专门讲解AES的文章。

1.读取并转换数据

首先是使用<input type="file"/>获取File对象,然后使用FileReader读取文件的二进制数据,最后再将读取到的ArrayBuffer转换为Crypto.js定义的WordBuffer进行加密就可以了。

<input type="file" onChange={this.handleFileChanged}/>

handleFileChanged = e => {
    const { files } = e.target;
    const reader = new FileReader();
    reader.readAsArrayBuffer(files[0]);
    reader.onload = event => {
        const arrayBuffer = event.target.result;
        const wordBuffer = ArrayBufferToWordArray(arrayBuffer);

        const AESKey = '30980f98296b77f00a55f3c92b35322d898ae2ffcdb906de40336d2cf3d556a0';
        const AESIv = 'e5889166bb98ba01e1a6bc9b32dbf3e6';
        const encData = AESEncData(wordBuffer, AESKey, AESIv);
        const fileBlob = new Blob([encData], { type: 'application/txt' });
        uploadFile(fileBlob, files[0].name);
    }
}

这里之所以会涉及到ArrayBuffer转WordBuffer是因为,Crypto.js在使用AES加密是支持对string以及WordBuffer进行加密,如果直接使用string进行加密的话,后续还涉及到一些编码的问题,处理起来会相对复杂一些,所以就将二进制数据转为WordBuffer后再进行AES加密。

2.加密和解密

在进行AES加密和解密时有以下几点需要额外注意一下:

  1. 要确保在加密和解密时使用的Key和Iv是一致的,如果调用 CryptoJS.AES.encrypt 时未传递Iv进来的话,会随机生成一个Iv,这样就会导致解密的时候无法正确的解出来。
  2. 最好确保你的Key是128、192或是256位的,如果Key是字符串类型,在传入加密函数前要先转为二进制类型的数据。
  3. 加密端和解密端在选择AES加密模式和填充模式时要保持一致,这里使用的是CBC和Pkcs7。
export const ArrayBufferToWordArray = arrayBuffer => {
    const u8 = new Uint8Array(arrayBuffer, 0, arrayBuffer.byteLength);
    const len = u8.length;
    const words = [];
    for (let i = 0; i < len; i += 1) {
        words[i >>> 2] |= (u8[i] & 0xff) << (24 - (i % 4) * 8);
    }
    return CryptoJS.lib.WordArray.create(words, len);
}

export const WordArrayToArrayBuffer = wordArray => {
    const { words } = wordArray;
    const { sigBytes } = wordArray;
    const u8 = new Uint8Array(sigBytes);
    for (let i = 0; i < sigBytes; i += 1) {
        const byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
        u8[i] = byte;
    }
    return u8;
}

export const AESEncData = (data, key, iv) => { // 这里的data是WordBuffer类型的数据
    const bKey = CryptoJS.enc.Hex.parse(key);
    // const bKey = CryptoJS.enc.Utf8.parse(key);
    const bIv = CryptoJS.enc.Hex.parse(iv);
    // const bIv = CryptoJS.enc.Utf8.parse(iv);

    const encrypt = CryptoJS.AES.encrypt(data, bKey, { iv: bIv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
    const arrayBuffer = WordArrayToArrayBuffer(encrypt.ciphertext);
    return arrayBuffer;
}

export const AESDecData = (data, key, iv) => { // 这里的data是WordBuffer类型的数据
    const bKey = CryptoJS.enc.Hex.parse(key);
    // const bKey = CryptoJS.enc.Utf8.parse(key);
    const bIv = CryptoJS.enc.Hex.parse(iv);
    // const bIv = CryptoJS.enc.Utf8.parse(iv);
    const decrypt = CryptoJS.AES.decrypt({ ciphertext: data }, bKey, { iv: bIv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
    const arrayBuffer = WordArrayToArrayBuffer(decrypt);
    return arrayBuffer;
}

结束语

至此AES加密文件的叙述就完毕了,如果本篇文章有描述错误的地方,请大家多多指正,感谢。