大家好,欢迎来到IT知识分享网。
–
最近团队遇到一个小需求,存在两个系统 A、B,系统 A 支持用户在线制作皮肤包,制作后的皮肤包用户可以下载后,导入到另外的系统 B 上。皮肤包本身的其实就是一个 zip 压缩包,系统 B 接收到压缩包后,解压并做一些常规的校验,比如版本、内容合法性校验等,整体功能也比较简单。
但没想到啊,一帮测试人员对我们开发人员一顿输出,首先绕过系统 A 搞了几个视频文件,把后缀改成 zip
就直接想上传,系统 B 每次都是等到上传完后才发现文件不合法,系统 B 在文件没上传完前又无法解压,也不知道文件内容是不是合法的,就这么消耗了大量带宽、大量时间后才提示用户皮肤包有问题。
这里涉及了两个问题,我们来捋一捋:
- 文件如何做加密,这样用户便无法去逆向,压缩包内部的敏感信息不会泄露出去。
- 服务端在接收到信息流时,在未传输完时如何去判断压缩包的合法性,提前告知用户。
AES VS RSA
说到加密,自己很多人会想到对称算法 AES[2] 以及非对称算法 RSA[3]。这两种算法按字面意思也较好理解,对称加密技术说白一点就是加密跟解密使用的是同一个密钥,这种加密算法速度极快,安全级别高,加密前后的大小一致;非对称加密技术则有公钥PK
、私钥SK
,算法的原理在于寻找两个素数,让他们的乘积刚好等于一个约定的数字,非对称算法的安全性是依赖于大数的分解,这个目前没有理论支持可以快速激活成功教程,它的安全性完全依赖于这个密钥的长度,一般用 1024 位已经足够使用。但是它的速度相比对称算法慢得多,一般仅用于少量数据的加密,待加密的数据长度不能超过密钥的长度。
使用 AES 对文件加密
结合这两种加密方式的优缺点,我们采用 AES 对文件本身做加解密,使用 AES 的原因主要考虑如下:
- 加解密性能问题,AES 的速度极快,相比 RSA 有 1000 倍以上提升。
- RSA 对源文有长度的要求,最大长度仅有密钥长度。
AES 的加密算法 Node.js 的crypto[4]模块中已经有内置,具体的使用可以参考官方文档。
AES 加密逻辑
const crypto = require(‘crypto’);
const algorithm = ‘aes-256-gcm’;
/
* 对一个buffer进行AES加密
* @param {Buffer} buffer 待加密的内容
* @param {String} key 密钥
* @param {String} iv 初始向量
* @return {
{key: string, iv: string, tag: Buffer, context: Buffer}}
*/
function aesEncrypt (buffer, key, iv) {
// 初始化加密算法
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(buffer);
let end = cipher.final();
// 生成身份验证标签,用于验证密文的来源
const tag = cipher.getAuthTag();
return {
key,
iv,
tag,
buffer: buffer.concat([encrypted, end]);
};
}
AES 解密逻辑
解密整体跟加密一样,只是接口换个名字即可:
const crypto = require(‘crypto’);
const algorithm = ‘aes-256-gcm’;
/
* 对一个buffer进行AES解密
* @param {
{key: string, iv: string, tag: Buffer, buffer: Buffer}} ret 待解密的内容
* @param {String} key 密钥
* @param {String} iv 初始向量
* @return {Buffer}
*/
function aesDecrypt ({key, iv, tag, buffer}) {
// 初始化解密算法
const decipher = crypto.createDecipheriv(algorithm, key, iv);
// 生成身份验证标签,用于验证密文的来源
decipher.setAuthTag(tag);
let decrypted = decipher.update(buffer);
let end = decipher.final();
return Buffer.concat([decrypted, end]);
}
AES 具体使用
有了上述两个接口后,我们便可实现一个简单的对称加密了:
const key = ‘abcdefghijklmnopqrstuvwxyz’; // 32 共享密钥,长度跟算法需要匹配上
const iv = ‘abcdefghijklmnop’; // 16 初始向量,长度跟算法需要匹配上
let fileBuffer = Buffer.from(‘abc’);
// 加密
let encrypted = aesEncrypt(fileBuffer, key, iv);
// 解密
let context = aesDecrypt(encrypted);
console.log(context.toString());
一般情况下,这个密钥较为重要,如果发生泄露则加密失去意义,所以key
、iv
会使用随机数动态生成,比如:
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
通过上述的调整后,加解密文件是比较容易的,回到我们的业务系统上面,系统 A 生成的压缩包,最终是需要给系统 B 使用,两个系统是隔离的,那这样 key
、iv
如何传输到系统 B 上面呢,况且还是动态生成的,生成出来 key
系统 B 是不知道的。
读到这聪明的你可能会想到,在把压缩包给到 B 的时候,顺便把 key
、iv
一同提交过去不就可以了,但细想了下,这个肯定不能明文把这个密钥发送过去,要不这个加密意义何在。
这时便需要用上 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.pem
、public.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 在读取这个文件后仍然是需要全部接收,再解压到临时目录,读取内容后才可以做校验,这样问题仍然解决不了。
除此之外,还有另外一个常见的需求,产品一般希望在浏览器侧在文件上传时就先做初步的解析,把明显不合法的文件提示到用户,这样用户体验更好。
这个问题的解决方案也不难,这些所有额外的信息都是可以把它当成二进制插入到文件的头部上的,比如:
包字段描述:|—-插入的额外信息—-|—-后面才是真正的文件内容—-|
二进制文件:00xxxxxxxxxxxxxxxxxxxxxxxxxxxx
文件头字段设计
我们把这些所有信息,按一定的格式,使用二进制的方式全部串连在一起,最终交付的只有一个组合过的文件,比如:
// 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 = {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
结尾
学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。
资料领取方式:戳这里获取
中…(img-daOyKcGL-26)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-hEbAedvR-26)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
结尾
学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。
资料领取方式:戳这里获取
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/140912.html