V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
xuthus
V2EX  ›  JavaScript

浏览器上进行文件 AES 加密解密

  •  
  •   xuthus · 2020-02-23 19:50:23 +08:00 · 6216 次点击
    这是一个创建于 1764 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我觉得这是一个奇葩的问题,但是我现在有这样的需求,文件上传前,先在浏览器上进行 AES 加密,在文件下载时,先进行 AES 解密再存储本地。我该如何操作,我目前正在 VUE 项目中尝试这些操作,我使用https://github.com/brix/crypto-js 来进行操作,感觉它非常流行,但是发现我只能对文本文件进行处理,对于图片之类的文件我无法操作。 附上我的部分代码

    Encode() {
                    if (this.file === null) {
                        console.log('file not exist!')
                    }
                    var CryptoJS = require('crypto-js');
                    this.file_mime = this.file.type;
                    this.file_name = this.file.name;
                    //读取本地文件
                    var reader = new FileReader();
                    //读取完毕后触发
                    reader.onload = () => {
                        let key = '1234567887654321';
    
                        var encrypted = CryptoJS.AES.encrypt(reader.result,key).ciphertext.toString();
    
                        this.file2 = new Blob([encrypted], {type: 'application/octet-stream'});
                        const a = document.createElement("a");
                        const url = window.URL.createObjectURL(this.file2);
                        const filename = this.file_name;
                        a.href = url;
                        a.download = filename;
                        a.click();
                        window.URL.revokeObjectURL(url);
                    };
                    reader.readAsDataURL(this.file);
                },
    
    Decode() {
                    if (this.file === null) {
                        console.log('file not exist!')
                    }
                    var CryptoJS = require('crypto-js');
                    //读取本地文件
                    var reader = new FileReader();
                    //读取完毕后触发
                    reader.onload = () => {
                        let key = '1234567887654321';
    
                        var decrypted = CryptoJS.AES.decrypt(reader.result,key).toString(CryptoJS.enc.Utf8);
    
                        //Blob 生成
                        this.file2 = new Blob([decrypted], {type: this.file_mime});
                        const a = document.createElement("a");
                        const url = window.URL.createObjectURL(this.file2);
                        const filename = this.file_name;
                        a.href = url;
                        a.download = filename;
                        a.click();
                        window.URL.revokeObjectURL(url);
                    };
                    reader.readAsText(this.file);
                }
    
    23 条回复    2020-02-24 13:52:38 +08:00
    ysc3839
        1
    ysc3839  
       2020-02-23 19:54:54 +08:00 via Android
    找支持 Blob 的加密库?
    also24
        2
    also24  
       2020-02-23 19:58:25 +08:00 via Android
    CryptoJS 可以解密 bin 的吧…

    // 不信你看电子工业出版社的 pdf
    xuthus
        3
    xuthus  
    OP
       2020-02-23 20:01:44 +08:00
    @ysc3839 谢谢,我找找。

    @also24 主要是我 JS 水平不够,出现的问题无法解决,即使我是面向搜索引擎编程
    tealover007
        4
    tealover007  
       2020-02-23 20:05:19 +08:00
    加密是一个很费 CPU 计算的操作。
    加密图片的意义在什么地方,是为了防止没有权限的人获取?
    换一个思路:如果远程服务器可以存放未加密的图片,那系统用权限控制就好。
    估计上面的方案不是你想要的,那么
    再换一个思路:利用离线 aes 加解密软件加密所需文件,把 aes 解密工具和加密后的文件上传到系统,对方下载 aes 加解密软件和文件,然后自己解密。文件的存放路径或者业务场景处理好,应该问题不大。
    多了一个下载解密软件和解密的操作。
    also24
        5
    also24  
       2020-02-23 20:11:41 +08:00
    @xuthus #3
    你 decrypt 之后,为什么要 tostring ?
    also24
        6
    also24  
       2020-02-23 20:12:54 +08:00
    另:你可能没有理解电子工业出版社的梗

    你可以点击下面链接里的 『立即阅读』,它的在线阅读器里的 pdf 就是用 JS 做的 AES 解密:
    https://yd.51zhy.cn/ebook/web/newBook/queryNewBookById?id=64556589
    xuthus
        7
    xuthus  
    OP
       2020-02-23 20:16:02 +08:00
    @tealover007 这确实是最优方案。但是我在写我的毕业设计[信息安全专业],是一个关于网盘类型的项目。我有想使用访问控制的手段来隔离,但是被要求文件存储需要进行加密 /解密处理,这种用户自行加密解密显然不太合适。
    xuthus
        8
    xuthus  
    OP
       2020-02-23 20:17:19 +08:00
    @also24 CryptoJS.AES.encrypt(reader.result,key) 这个对象无法写入 Blob,所以我就把他 tostring 了。
    also24
        9
    also24  
       2020-02-23 20:17:51 +08:00
    另外,我看了下,你的 AES 似乎没有指定 mode ?

    你可以需要了解一下 AES 的几种 mode
    https://zh.wikipedia.org/zh-hans/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F
    xuthus
        10
    xuthus  
    OP
       2020-02-23 20:21:34 +08:00
    @also24 我在正常测试时,是有指定的,CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7}) 这种,CryptoJS 有提供,当然,流处理上,我会选计算器模式 CTR。
    xuthus
        11
    xuthus  
    OP
       2020-02-23 20:26:34 +08:00
    @also24 我看了电子工业出版社的 pdf,发现确实是,Key 和 FileURL 都给返回了。
    also24
        12
    also24  
       2020-02-23 20:35:19 +08:00
    https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader

    我看了一下 FileReader,在读取的时候需要指定
    FileReader.readAsArrayBuffer()
    FileReader.readAsBinaryString()
    FileReader.readAsDataURL()

    你在 encode 方法里用的是 readAsDataURL,也就是说 reader.result 里其实是文件在 base64 编码后的字符串。
    而你把这个字符串直接传进了 encrypt 方法,也就是说此时你其实是在加密这个字符串。
    encrypt 方法返回的 encryptedData 直接 tostring 的话,是密文 base64 后的字符串,
    但你取的是 encryptedData.ciphertext.tostring(),这个实际上是密文的 hexString 字符串。

    相应的,你在 Decode 方法里使用的是 readAsText,文件内容是字符串的情况下也不算太错
    但是你把这串文本直接丢进 decrypt 方法就大错特错了,应当从 hexString 转回 Blob 才对


    当然,以上是针对你的程序来说的。
    我来写的话,应该会选择 readAsDataURL 读到 base64 编码后的文件,
    然后 base64 解码后丢进 encrypt 方法,再把 encryptedData.toString() 的字符串解码后丢进输出文件。
    LuRenJiasWorld
        13
    LuRenJiasWorld  
       2020-02-23 20:39:40 +08:00
    有个建议,试试看 wasm 的库,比如这个:
    https://github.com/flash1293/aes-wasm

    这些库一般都是从成熟的 C/C++库 port 过来的,缺点是一般都要传入 ArrayBuffer,处理起来费一点代码
    xuthus
        14
    xuthus  
    OP
       2020-02-23 20:42:19 +08:00
    @also24 你的解读是正确的,我的思路一开始就是:先得到 base64Data->aes 加密 base64Data->存为一个文本。解密直接读取文本->aes 解密->base64Data 解码。
    xuthus
        15
    xuthus  
    OP
       2020-02-23 20:45:38 +08:00
    @LuRenJiasWorld 谢谢,我去研究研究。
    also24
        16
    also24  
       2020-02-23 20:51:07 +08:00   ❤️ 6
    总结一下,请务必分清楚几种不同的数据格式:

    一个字符串,它的内容是:『 V2EX 』

    对应的 ASCII 码是:
    86 50 69 88

    对应的十六进制分别为:
    56 32 45 58

    那么在 UTF8 / ANSI 编码下,按大端序书写,它的二进制( binary )内容是:
    0101 0110 0011 0010 0100 0101 0101 1000

    相应的 HEX 字符串就是:
    0x56324558

    相应的 base64 字符串是:
    VjJFWA==

    你的错误就在于混淆使用了 binary / b64 String / HEX String,从而导致混乱

    不过确实,网上找到的很多资料都在混淆使用或者不仔细说明。
    事实上,AES 的 encrypt 方法只管你输入的是 binary( byte array ) 就好了,字符串还是文件对它来说没区别。

    总之,你这里只需要配合 FileReader,选择正确的编解码方式就好了。
    个人不建议对 base64 String 进行加解密操作,这样会造成密文文件体积增大,徒增开销。
    如果我没有理解错的话,直接使用 FileReader.readAsArrayBuffer() 应该能够避免中间 b64 编解码的开销。
    xuthus
        17
    xuthus  
    OP
       2020-02-23 20:53:44 +08:00
    @also24 谢谢你的建议,我确实是混淆了类型,我去试试看!
    tyx1703
        18
    tyx1703  
       2020-02-23 21:29:13 +08:00
    正好前两天看过这个

    我用的这个库 https://cryptojs.gitbook.io/docs/#encoders。
    首先读取文件用 FileReader.readAsArrayBuffer(),然后把 ArrayBuffer 转成十六进制字符串。
    再用 crypto-js 读取十六进制字符串进行加密。
    加密过程结束之后,crypto-js 把结果转成十六进制字符串,然后转换成 ArrayBuffer,写入文件对象即可。
    xuthus
        19
    xuthus  
    OP
       2020-02-23 22:09:13 +08:00
    @tyx1703 谢谢你的提示,我将尝试你的建议。
    muzuiget
        20
    muzuiget  
       2020-02-24 00:12:11 +08:00
    为何上面两人的语法像机器翻译的样子?
    kaicity
        21
    kaicity  
       2020-02-24 00:38:50 +08:00 via Android
    @muzuiget +1,外网+翻译即视感
    wizardoz
        22
    wizardoz  
       2020-02-24 10:06:43 +08:00
    https 不是已经做了这个事了吗?不相信浏览器?
    also24
        23
    also24  
       2020-02-24 13:52:38 +08:00 via Android   ❤️ 1
    @wizardoz
    https 保证的是传输过程中的安全性,服务端可以解密出完整的明文数据。
    楼主这样做之后,发给服务端的也是密文,服务端在没有密码的情况下,只负责存储,无法解密。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1605 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 16:32 · PVG 00:32 · LAX 08:32 · JFK 11:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.