nodejs 前后端数据加密方案
前言
即时没有深入研究过密码学,但是对于数据加密,我想应该很多同学都有所接触吧。
虽然对于前端来说,其实数据加密的意义并不大。你代码都给人家了,只要有技术,有耐心,破解就只是时间的问题了。
在对待前端加密的态度上面,我始终秉承着一句话:“防小人,不防君子”。
但是奈何,很多时候,你不想做这么白费心力的事情,但是老板不答应。
我本将心向明月,奈何明月照沟渠。
很多时候,在工作中,都是人在江湖,身不由己!
之前在工作中曾经有这方面的需求,因此稍微研究了下。
如何加密
对于加密的情形,我们需要区分好几种情况,比如是对数据进行加密呢,还是对服务器请求地址的 path 进行加密。
虽然应用的情况不同,但是解决的思路都是大同小异的。
最初级的方案当然是,自己写个简单的加密算法,比如数据是 “1234567890”,我做一个映射,映射到 ‘0987654321',将顺序打乱,一般就很难看出数据的原格式是什么了,除非别人知道我的映射规则。
上面的加密方案几乎是所有数据加密的基础原理,毫不夸张的说,无出其右。
但是我们的初级方案有一个致命的缺点,它太基础了,我们只是考虑到了数据中数字的情况,但是一旦数据中出现别的字符,比如中文、英文字符、希腊字母,我们上面的映射方法就抓瞎了。
我们当然可以进一步对我们的数据映射规则进行扩充,但是这个构成前人早就给我们做好了,因为我们大可不必自己冲头开始造这么基础的轮子。
这就跟盖房子一样,我们可以从烧制砖块开始干,也可以选择购买成品砖块,加快我们的建造速度。
所以,现在我们应该明白了,加密的关键是要有一套好的加密算法,我们直接拿来用就行了。
常见的加密算法有:DES加密算法、AES加密算法、RSA加密算法、Base64加密算法、MD5加密算法、SHA1加密算法、XXXTEA加密算法等等。
对于这些算法,感兴趣的同学,可以自己查找资料,去深入的学习其原理。
当然,对其我们只需要有个基本的了解就行了,不推荐太过深入,甚至于想自己去实现一套。
有这个研究的想法当然是好的,但是不推荐具体去行动,不仅费时还费心力健壮性还不行,等你研究好了,老板可能就请你打包离开公司了。
况且,前端本身就有非常成熟的开源工具:crypto-js,我们直接拿来用就行了。
对应的 nodejs 后端,当然也可以用这个工具包,但是更好的选择是采用 nodejs 原生支持的 crypto,用法其实也大同小异。
后端(nodejs)
比如我们想采用 aes-128-cbc 加密算法,可以像下面这和么用:
/**
* 加密
* @param {string} data 原文
* @param {string} key CipherKey
* @param {string} iv
*/
const aesEncrypt = (data, key = global._config.secretKey, iv = global._config.secretIv) => {
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
let crypted = cipher.update(data, 'utf8', 'hex');
crypted += cipher.final('hex');
return crypted;
};
/**
* 解密
* @param {string} encrypted 密文
* @param {string} key CipherKey
* @param {string} iv
*/
const aesDecrypt = (encrypted, key = global._config.secretKey, iv = global._config.secretIv) => {
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
};
封装一个加密的方法,一个解密的方法,当然密钥必须一样。
为了使安全性更高,这里我们选择采用随机生成 key 和 iv,而前端可以在初始化的时候,发请求向后端请求,拿到后端服务器每次运行的时候动态生成的 key 和 iv。
如果想用别的加密算法,可以了解下 nodejs 的 api:
前端
其实前后端是相互独立的,但是你必须得采用相同的加密算法,我们这里就以 aes-128-cbc 加密算法来作为例子。
当然,我们采用的就是前面说的 crypto-js。
我们同样需要对加密和解密进行封装,而 key 和 iv,我们都需要从服务器进行获取。
/**
* 解密文本
* @name decryptText
* @function
* @param {string} encryptText 需要解密的文本
* @returns Promise
*/
export const decryptText = async (encryptText) => {
/** 如果没有密钥,需要先从服务器获取密钥 */
if (secretKey === undefined && secretIv === undefined) {
try {
await obtainKeyFromServer();
} catch (err) {
throw new Error(err);
}
}
let text = AES.decrypt(encryptText, CryptoJS.enc.Utf8.parse(secretKey), {
iv: CryptoJS.enc.Utf8.parse(secretIv),
mode: CryptoJS.mode.CBC,
format: CryptoJS.format.Hex,
}).toString(CryptoJS.enc.Utf8);
try {
text = JSON.parse(text);
} catch (err) {
console.log(err);
}
return text;
};
/**
* 加密文本
* @name encryptText
* @function
* @param {string | object | array} text 需要加密的文本
*/
export const encryptText = async (text) => {
/** 如果没有密钥,需要先从服务器获取密钥 */
if (secretKey === undefined && secretIv === undefined) {
try {
await obtainKeyFromServer();
} catch (err) {
throw new Error(err);
}
}
if (!isString(text)) text = JSON.stringify(text);
return AES.encrypt(text, CryptoJS.enc.Utf8.parse(secretKey), {
iv: CryptoJS.enc.Utf8.parse(secretIv),
mode: CryptoJS.mode.CBC,
format: CryptoJS.format.Hex,
}).toString();
};
加密 url
如果只是想加密数据,直接调用上面封装好的几个方法进行处理就好了。
但是有时候,老板让你对url也进行加密处理,你该怎么搞呢。
其实原理是一样的,无非加密的对象由数据变成了 url 了而已。
这里面有几个关键的地方,值得我们注意的。
前端,我们可以通过 window.location 对象,获取到 href,然后从里面拿到 host, port, path 以及 params 等等吃参数。
我们来分析一下,一个合格的 url 的格式一般是这样的:
image.png
主机和端口号我们肯定加密不了,所以只有在 path 和 params 上下功夫了。
在进行加密的时候,需要注意的地方是,我们要对 path 和 params 分开进行加密,因为后端需要对加密的 path 进行解密,用以区分我们发送的是什么请求。而 params 一般是我们在发送 get 请求的时候,携带的信息,所以需要单独进行加密。
后记
其实,前后端加密数据,意义并没有那么大,只要有心人想要破解,还是没有太大难度的。
区别在于,你用的加密算法越复杂,你就需要花费更多的时间去执行加密、解密操作。
而保密的最关键点在于,不能泄漏了密钥和算法,否则数据就等同于不设防了。