背景
最近团队遇到一个小需求,存在两个系统 A、B,系统 A 支持用户在线制作皮肤包,制作后的皮肤包用户可以下载后,导入到另外的系统 B 上。皮肤包本身的其实就是一个 zip 压缩包,系统 B 接收到压缩包后,解压并做一些常规的校验,比如版本、内容合法性校验等,整体功能也比较简单。
但没想到啊,一帮测试人员对我们开发人员一顿输出,首先绕过系统 A 搞了几个视频文件,把后缀改成 zip 就直接想上传,系统 B 每次都是等到上传完后才发现文件不合法,系统 B 在文件没上传完前又无法解压,也不知道文件内容是不是合法的,就这么消耗了大量带宽、大量时间后才提示用户皮肤包有问题。
这里涉及了两个问题,我们来捋一捋:
文件如何做加密,这样用户便无法去逆向,压缩包内部的敏感信息不会泄露出去。
服务端在接收到信息流时,在未传输完时如何去判断压缩包的合法性,提前告知用户。
AES VS RSA
说到加密,自己很多人会想到对称算法 AES 以及非对称算法 RSA。这两种算法按字面意思也较好理解,对称加密技术说白一点就是加密跟解密使用的是同一个密钥,这种加密算法速度极快,安全级别高,加密前后的大小一致;非对称加密技术则有公钥PK、私钥SK,算法的原理在于寻找两个素数,让他们的乘积刚好等于一个约定的数字,非对称算法的安全性是依赖于大数的分解,这个目前没有理论支持可以快速破解,它的安全性完全依赖于这个密钥的长度,一般用 1024 位已经足够使用。但是它的速度相比对称算法慢得多,一般仅用于少量数据的加密,待加密的数据长度不能超过密钥的长度。
使用 AES 对文件加密
结合这两种加密方式的优缺点,我们采用 AES 对文件本身做加解密,使用 AES 的原因主要考虑如下:
加解密性能问题,AES 的速度极快,相比 RSA 有 1000 倍以上提升。
RSA 对源文有长度的要求,最大长度仅有密钥长度。
AES 的加密算法 Node.js 的crypto模块中已经有内置,具体的使用可以参考官方文档。
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