浏览器端搞些小儿科的加密,就好比在黑暗夜空中,点缀了几颗星星,告诉黑客「这里有宝贵信息,快来翻牌」

浏览器端的加密,都是相对安全的。
它的具体安危,取决于里面存在的信息价值,是否值得破解者出手一试。
就跟那个经典的笑话一样:
某个客户自己开发了一套软件,并买了一个防破解插件,让大牛测试破解难度。
三天后大牛郁闷地说这还挺难搞的,居然花了 3 天。
仔细一看原来是把人家的防破解插件给破了……
其实 JavaScript + Go 的加解密,真实代码调用很简单。
我和服务端小伙伴对着网上资料,库库一顿操作就搞定了。

所以在进入代码环节之前,我们先对齐同步一些基本信息,避免其他想了解这块知识点的小伙伴,捋不清里面逻辑。
所谓「加密」,就是将你的信息,通过「某种处理」,让它变成不可读,被保护不被非法获取。
而「解密」,就是将处理后的信息,再转换成正常人能读懂的信息。
举个栗子,你要去拿货,对方来了一句「天王盖地虎」。
你是不是要接话,来表明你的身份?

“对暗号”,其实就是一种处理信息的方式,你只有回答出对方的问题,才能进一步操作。
为了「安全」。
为什么安全要括起来,因为没有绝对的安全。
在浏览器端做数据加解密,只能保证:
注意!以上 2 点只能防抓包,不防注入。
意思就是对方通过 JavaScript 脚本注入,替换你原先写的代码。

这样就可以在你加密信息之前,获取到原信息。
所谓「对称加密」,意思就是大家拿到同一把钥匙,对传递的数据进行加解密。
举个栗子,对称加密就像你和朋友买了一个保险柜并配了 2 把一样的钥匙。
每次你找 ta 聊天,你把信写好,丢到保险柜,把保险柜扔给对方。
然后对方拿一样的钥匙,就可以开保险柜,并把他的信也丢到里面仍回给你。
再举个栗子,高中同学 A 和 B 谈恋爱了,但是他们纸条不能直接写情书呀,要不然被发现就哦豁了。
所以他们想了个法子,写纸条的时候,只写数字,比如 108-1,意思就是找某本书的 108 页第 1 个字。

看,他们的钥匙,就是相同的书,这样信息就等同了,对称了。
常见的对称加密算法:DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6 和 AES。
所谓「非对称加密」,就是 A 生成一对密钥,将公钥给别人,然后私钥自己藏好,别人通过公钥加密信息给 A,A 拿自己的私钥解密。
举个栗子,就是甲方是养蛊的,ta 有一对子母蛊。
甲方会把子蛊给乙方,母蛊留给自己。
这样哪怕子蛊丢了,别人拿子蛊传递信息,也只有甲方才能通过母蛊获取。
这种情况只有甲方才能破解信息,别人都没法理解不同人拿子蛊传递的信息。

所以非对称加密可以用在松鼠党上:我可不管谁拿了子蛊,只要传递信息给我,我就都存起来。
对称加密算法的运行速度比非对称加密算法快,所以需要加密大量的数据时,建议采用对称加密算法,提升加解密速度。
常见的非对称加密算法:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
所谓哈希(Hash)算法,其实就是将信息做一个不可逆的转换,然后将信息存储起来。
举个栗子,小明同学将自己的密码,通过哈希(Hash)算法,存到了数据库里。
这样别人监听了小明登录账号的接口,也没法获取到里面的内容。
又或者别人看到了数据库,也不知道小明同学记录的原始密码是什么。
哈希(Hash)算法中,MD5 因为计算机算力提升,可以快速找到一个 MD5 的值对应的原文,所以大家由 MD5 的使用变成了 SHA-256 的使用,提升了加密的安全性。

当然,如果小伙伴们的密码比较简单,例如 123456 或者 666666 这种,被破解的还是大有可能的,可以在线试试 https://tool.oschina.net/encrypt?type=2
常见的哈希算法:MD2、MD4、MD5、HAVAL、SHA、SHA-1、HMAC、HMAC-MD5、HMAC-SHA1
在上面中,我们知道加密算法,大致分为「对称加密」和「非对称加密」以及「哈希(Hash)算法」这 3 种类型。
其中哈希(Hash)算法在对接中不可用了,因为它一般用作存储,不解出来。
而简单的对称加密和非对称加密,可能又过不了安全审查。
所以再三考虑下,我们采用 AES + RSA(对称加密 + 非对称加密)这种加密方式,提高数据传输的安全性。
RSA + AES 的方案,我们采用这种形式:

这一块的困难点在于 Go,因为 JavaScript 更多用的是一个库,即 jsencrypt。
前端的 RSA 加密,是:
jsencrypt,设置公钥代码如下:
import JSEncrypt from 'jsencrypt';
const encryptor = new JSEncrypt();
/**
* @name RSA-设置公钥
* @param val 公钥
*/
export const setPublicKey = (val: string) => {
encryptor.setPublicKey(val);
}
/**
* @name RSA-加密
* @param data 待加密数据
* @returns {PromiseLike<ArrayBuffer>} 返回加密字符串
*/
export const rsaEncrypt = (data: string) => {
return encryptor.encrypt(data) || '';
}服务端的 RSA 解密,是:
Go 的代码可参考:
因为 Go 这边的库可能比较原始,所以如果碰到问题的话,大概率可能是公私钥的生成有问题,它需要按照最原始的标准格式来生成。
对这一块确实没啥经验,网上说的文章,要么单独讲 JavaScript 的,要么单独讲 Go 的,整个人看完都懵圈。
好在网上大佬也确实给力,我跟服务端大佬合计了下,将一些点给撇掉了,直接采用:
实际开发中使用 AES 加密解密需要注意的地方:
首先,上面说这些可能有所信息误导,所以咱们直接看前端通过 JavaScript 生成 AES key 及加解密方法:
// 为什么不直接用 window.crypto,因为这个库做了兼容,可以看 https://github.com/brix/crypto-js/blob/4dcaa7afd08f48cd285463b8f9499cdb242605fa/src/core.js#L13
import CryptoJS from 'crypto-js';
/**
* @name AESKey
* @description 生成 AES Key
* @return 随机生成 16 位的 AES Key
*/
export const createAesKey = () => {
const expect = 16;
let str = Math.random().toString(36).substr(2);
while (str.length < expect) {
str += Math.random().toString(36).substr(2);
}
str = str.substr(0, 16);
return str;
}
/**
* @name AES-加密
* @param raw 待加密字段
* @param AESKey AES Key
* @return {string} 返回加密字段
*/
export const aesEncrypt = (raw: any, AESKey: string) => {
const cypherKey = CryptoJS.enc.Utf8.parse(AESKey);
CryptoJS.pad.ZeroPadding.pad(cypherKey, 4);
const iv = CryptoJS.SHA256(AESKey).toString();
const cfg = { iv: CryptoJS.enc.Utf8.parse(iv) };
return CryptoJS.AES.encrypt(raw, cypherKey, cfg).toString();
}
/**
* @name AES-解密
* @param raw 待解密数据
* @param AESKey 解密 key
* @returns {string} 返回解密字符串
*/
export const aesDecrypt = (raw: string, AESKey: string) => {
const cypherKey = CryptoJS.enc.Utf8.parse(AESKey);
CryptoJS.pad.ZeroPadding.pad(cypherKey, 4);
const iv = CryptoJS.SHA256(AESKey).toString();
const cfg = { iv: CryptoJS.enc.Utf8.parse(iv) };
const decrypt = CryptoJS.AES.decrypt(raw, cypherKey, cfg);
return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}然后,在上面这段代码,有几个关键信息指标需要统一:
mode:AES 有各种加密模式,例如 CBC、CFB、CTR 等模式,在前端的 crypto-js 说明文档里面可以看到它含有 6 种
iv:AES 在一些加密模式上,需要指定 IV,即初始向量
padding:AES 需要加密的数据,不是 16 的倍数的时候,需要对原本的数据做 padding 操作(即补全长度到固定的位数),它有 Pkcs7、AnsiX923 等种类

最后,服务端 Go 的代码可以参考:
package main
import (
"fmt"
"github.com/LinkinStars/go-scaffold/contrib/cryptor"
)
func main() {
key := "1234"
e := cryptor.AesSimpleEncrypt("Hello World!", key)
fmt.Println("加密后:", e)
d := cryptor.AesSimpleDecrypt(e, key)
fmt.Println("解密后:", d)
iv := cryptor.GenIVFromKey(key)
fmt.Println("使用的 IV:", iv)
}
// 输出
// 加密后: NHlpzbcTvOj686VaF7fU7g==
// 解密后: Hello World!
// 使用的 IV: 03ac674216f3e15c搜索关键词:浏览器端 rsa + aes
不折腾的前端,和咸鱼有什么区别!
觉得文章不错的小伙伴欢迎点赞/点 Star。
如果小伙伴需要联系 jsliang:
个人联系方式存放在 Github 首页,欢迎一起折腾~
争取打造自己成为一个充满探索欲,喜欢折腾,乐于扩展自己知识面的终身学习斜杠程序员。
jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。
基于 https://github.com/LiangJunrong/document-library 上的作品创作。
本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。