开发必懂的文件加解密(2)

const key = ‘abcdefghijklmnopqrstuvwxyz123456’; // 32 共享密钥,长度跟算法需要匹配上

const iv = ‘abcdefghijklmnop’;  // 16 初始向量,长度跟算法需要匹配上

let fileBuffer = Buffer.from(‘abc’);

// 加密

let encrypted = aesEncrypt(fileBuffer, key, iv);

// 解密

let context = aesDecrypt(encrypted);

console.log(context.toString());

一般情况下,这个密钥较为重要,如果发生泄露则加密失去意义,所以keyiv会使用随机数动态生成,比如:

const key = crypto.randomBytes(32);

const iv = crypto.randomBytes(16);

通过上述的调整后,加解密文件是比较容易的,回到我们的业务系统上面,系统 A 生成的压缩包,最终是需要给系统 B 使用,两个系统是隔离的,那这样 keyiv 如何传输到系统 B 上面呢,况且还是动态生成的,生成出来 key 系统 B 是不知道的。

读到这聪明的你可能会想到,在把压缩包给到 B 的时候,顺便把 keyiv 一同提交过去不就可以了,但细想了下,这个肯定不能明文把这个密钥发送过去,要不这个加密意义何在。

这时便需要用上 RSA 非对称加密技术了。

使用 RSA 算法对密钥再次进行非对称加密


RSA 的加密算法 Node.js 的 crypto 模块[5] 中已经有内置,具体的使用可以参考官方文档。

生成 RSA 的公钥与私钥

使用 openssl[6] 组件可以直接生成 RSA 的公钥私钥对,具体的命令可以参考:www.scottbrady91.com/OpenSSL/Cre…[7]。

# 生成私钥

openssl genrsa -out private.pem 1024

# 提取公钥

openssl rsa -in private.pem -pubout -out public.pem

这样生成出来的两个文件 private.pempublic.pem 就可以使用了,下面我们使用 Node.js 实现具体的加解密逻辑。

RSA 加密逻辑

const fs = require(‘fs’);

const crypto = require(‘crypto’);

const PK = fs.readFileSync(‘./public.pem’, ‘utf-8’);

/**

* 对一个buffer进行RSA加密

* @param {Buffer} 待加密的内容

* @return {Buffer}

*/

function rsaEncrypt (buffer) {

return crypto.publicEncrypt(PK, buffer);

}

RSA 解密逻辑

const fs = require(‘fs’);

const crypto = require(‘crypto’);

const SK = fs.readFileSync(‘./private.pem’, ‘utf-8’);

/**

* 对一个buffer进行RSA解密

* @param {Buffer} 待解密的内容

* @return {Buffer}

*/

function rsaDecrypt (buffer) {

return crypto.privateDecrypt(SK, buffer);

}

RSA 具体使用

有了上述接口后,便可对 AES 的密钥进行加密后再传输,服务器 B 保存好 RSA 私钥 ,服务器 A 则可以直接用 RSA 公钥 对数据加密后再发送,结合刚 AES 的逻辑后,如下:

/**

* 加密文件

* @param {Buffer} fileBuffer

* @return {{file: Buffer, key: Buffer}}

*/

function encrypt (fileBuffer) {

const key = crypto.randomBytes(32);

const iv = crypto.randomBytes(16);

const { tag, file } = aesEncrypt(fileBuffer, key, iv);

return {

file,

key: rsaEncrypt(Buffer.concat([key, iv, tag]));     // 由于长度是固定的,直接连在一起即可

};

}

/**

* 解密文件

* @param {{file: Buffer, key: Buffer}}

* @return {Buffer}

*/

function decrypt ({file, key}) {

const source = rsaDecrypt(key).toString();

const k = source.slice(0, 32);

const iv = source.slice(32, 48);

const tag = source.slice(48);

return aesDecrypt({

key: k,

iv,

tag,

buffer: file

})

}

这样结合在一起后,服务器 A 生成的压缩包,只要包含好 {file, key} 这两块内容,服务器 B 便可把文件解密出来了,这样基本上实现了我们第一点的目标:1. 文件如何做加密,这样用户便无法去逆向,压缩包内部的敏感信息不会泄露出去

但还遗留了另外一个问题需要解决:2. 服务端在接收到信息流时,在未传输完时如何去判断压缩包的合法性,提前告知用户

关于解密的还可以看看:记一次破解前端加密详细过程

优化加密文件


按上面的加密方式,输出的结果是一个 buffer文件 内容,以及一个 加密过的key,除了这些信息外,一般这个 buffer文件 压缩包还会有一些额外的信息,比如:版本号、压缩包生成时间,描述信息等。这些信息按常规的方式,可能是分成几个文件,然后再打一个压缩包把文件放在一起,比如:

// zip file

- pkg

manifest.json       // 额外的信息

key.json            // 保存了加密过的密钥

file.json           // 加密过的文件

但如果用这种方式保存,一般情况下还要对这个 zip文件 做下加密,然后改下后缀名,但是服务器 B 在读取这个文件后仍然是需要全部接收,再解压到临时目录,读取内容后才可以做校验,这样问题仍然解决不了。

除此之外,还有另外一个常见的需求,产品一般希望在浏览器侧在文件上传时就先做初步的解析,把明显不合法的文件提示到用户,这样用户体验更好。

这个问题的解决方案也不难,这些所有额外的信息都是可以把它当成二进制插入到文件的头部上的,比如:

包字段描述:|----插入的额外信息----|----后面才是真正的文件内容----|

二进制文件:010101010101010101010xxxxxxxxxxxxxxxxxxxxxxxxxxxx

文件头字段设计

我们把这些所有信息,按一定的格式,使用二进制的方式全部串连在一起,最终交付的只有一个组合过的文件,比如:

// theme pkg.

0                8                16

|------flag------|–extra length–|

|----------extra data…----------|

|-------------data…-------------|

  • flag

固定标识 THEME,长度:8 byte,说明该压缩包为一个皮肤包,这样可以快速对压缩包进行识别

  • extra length

extra data 的真实长度,这是一个 16 进制的数据,长度:8 byte,说明插入的数据长度。比如:长度 35 的数据,转化为 16 进制后为 0x23,那这字段为 00000023

  • extra data

使用 RSA 加密过的数据,我们可以把上述需要用 RSA 加密的信息全部放在这里,比如 key 字段、版本号、描述信息等

  • data

使用 AES 加密过的数据,可以通过 extra data 里面保存的 key 把真实的数据全部解密出来

生成的新的加密文件

有了上面的理论基础后,马上可以实践起来,代码如下:

/**

* 加密文件

* @param {Buffer} fileBuffer

* @return {Buffer}

*/

function encrypt (fileBuffer) {

const key = crypto.randomBytes(32);

const iv = crypto.randomBytes(16);

const version = ‘v1.1’;

// 记录上所有额外的压缩外信息,比如版本号、原始的密钥

const extraJSON = {

version,

key,

iv

}

// 完成文件的AES加密,并输出身份验证标签

const { tag, file } = aesEncrypt(fileBuffer, key, iv);

extraJSON.tag = tag;

// 对 extraJSON 整个进行RSA加密

const extraData = rsaEncrypt(Buffer.from(JSON.stringify(extraJSON)));

const extraLength = extraData.length;

// 最终把所有数据合并在一起

return Buffer.concat([

Buffer.from(‘THEME’),

Buffer.from(Buffer.from(extraLength.toString(16).padStart(8, ‘0’))),

extraData,

file

]);

}

通过这种加密方式后,相关的信息都放在文件的头部上,我们可以不用对整个文件进行操作的时候,便可以轻松读取出来,对于解密其实就是一个反向的操作。

对新生成的文件进行解密

/**

* 解密文件

* @param {Buffer} fileBuffer

* @return {Buffer}

*/

function decrypt (fileBuffer) {

const type = fileBuffer.slice(0, 8);    // THEME

const extraLength = +(‘0x’ + fileBuffer.slice(8, 16).toString());

const extraDataEndIndex = 16 + extraLength;

// 对已经被RSA加密过的数据进行解密操作

const extraData = rsaDecrypt(fileBuffer.slice(16, extraDataEndIndex));

const extraJSON = JSON.parse(extraData);

// 最终使用AES再对剩下文件进行解密操作,即为最终的文件

return aesDecrypt({

key: extraJSON.key,

iv: extraJSON.iv,

tag: extraJSON.tag,

buffer: Buffer.slice(extraDataEndIndex)

});

}

使用这种方式处理后,在 RSA 解密出 extraData 的时候,就可以对整个文件进行各种校验,整个过程只要有异常说明文件已经被篡改,用这种方式比用压缩包会好很多,特别是文件体积庞大的时候,可以流式处理,发现不合理时即可马上阻止。

浏览器端如何解析该文件

由于现在整个文件格式都是二进制流,现代的浏览器都是有相应的能力去读取并做处理的,这样也可以在用户上传文件时先做一定的初步处理,体验会有比较大的提升

可以使用 DataView 的方式把二进制数据读取出来,详情可以参考:DataView[8],初步的实现如下:

/**

* 把二进制流转成对应ascii字符

* @param {DataView} dv         二进制数据库

* @param {Number}   start      起始位置

* @param {Number}   end        结束位置

* @return {String}

*/

function buffer2Char (dv, start, end) {

let ret = [];

for (let i = start; i < end; i++) {

let charCode = dv.getUint8(i);

let code = String.fromCharCode(charCode);

ret.push(code);

}

return ret.join(‘’);

}

function test () {

let fileDom = document.getElementById(‘file’);

let file = fileDom.files[0];

let reader = new FileReader();

reader.readAsArrayBuffer(file);

reader.addEventListener(“load”, function(e) {

let dv = new DataView(buffer);

let flag = buffer2Char(dv, 0, 8);   // THEME

var extraLength = +(‘0x’ + buffer2Char(dv, 8, 16));

var extraData = buffer2Char(dv, 16, extraLength);

console.log(flag, extraLength, extraData);

});

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

整理面试题,不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。

《前端基础面试题》,《前端校招面试题精编解析大全》,《前端面试题宝典》,《前端面试题:常用算法》PDF完整版点击这里免费领取

前端面试题宝典

前端校招面试题详解

2900035)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-FcHncH8t-1713782900036)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-e1TBA4c4-1713782900036)]

最后

整理面试题,不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。

《前端基础面试题》,《前端校招面试题精编解析大全》,《前端面试题宝典》,《前端面试题:常用算法》PDF完整版点击这里免费领取

[外链图片转存中…(img-EPqleWAl-1713782900036)]

[外链图片转存中…(img-SBh4bf1x-1713782900036)]

[外链图片转存中…(img-dtkDWFbJ-1713782900037)]

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值