前端加密cryptojs与JSEncrypt使实例详解

单向散列函数

在网站项目中,有时我们需要对传给后端的数据,比如 token 等进行加密处理。本文是对几种常见的前端加密方法,以及如何使用开源的加密库 crypto-js、JSEncrypt 来实现它们的分享。

又称为消息摘要算法,是不可逆的加密算法,即对明文进行加密后,无法通过得到的密文还原回去得到明文。一般所谓的比如 MD5 破解,其实是不断的尝试用不同的明文进行加密,直到得到的加密结果一致。

常见的单项散列函数有 MD5、SHA1、SHA256、SHA512 ,以及它们之前加上 Hmac(Keyed-hash message authentication codes) 后的 HmacMD5、HmacSHA1 等。下面以 MD5 为例重点介绍,其它几种则可以举一反三,不多赘述:

MD5

简单介绍

MD5 长度固定,不论输入的内容有多少字节,最终输出结果都为 128 bit,即 16 字节。这也就解释了为什么 MD5 以及其它单向散列函数是不可逆的 —— 输出定长代表会有数据丢失。

通常,我们可以用 16 进制字面值来表示它,每 4 bit 以 16 进制字面值显示,得到的是一个长度为 32 位的字符串。注意,MD5 等单向散列函数具有高度的离散性,意思是只要输入的明文不一样,得到的结果就完全不一样,哪怕是仅仅多了一个空格。

使用场景

MD5 有下面几种使用场景:

  • 可以用来做密码的保护。比如可以不直接存储用户的密码,而是存储密码的 MD5 结果。但是现在一般认为用 MD5 来做加密是不太安全的,更多是利用 MD5 的高度离散性特点,用它来做数字签名、完整性校验,云盘秒传等;
  • 数字签名。我们可以在发布程序时同时发布其 MD5。这样,别人下载程序后自己计算一遍 MD5,一对比就知道程序是否被篡改过,比如植入了木马。
  • 完整性校验。比如前端向后端传输一段非常大的数据,为了防止网络传输过程中丢失数据,可以在前端生成一段数据的 MD5 一同传给后端,这样后端接收完数据后再计算一次 MD5,就知道数据是否完整了。

使用 crypto-js 进行 MD5 加密

为了方便,我采用了在普通 html 页面直接引入 cdn 的方式来引入 crypto-js。


引入后,我们就能得到 CryptoJS 这个对象,它包含了很多方法,打印结果如下图:

前端加密cryptojs与JSEncrypt使实例详解_第1张图片

其中就定义了 MD5 方法和 algo 对象,借助它们,可以分别得到输入数据的 MD5 结果:

CryptoJS.MD5()

CryptoJS.MD5('2022JueJin').toString()

结果为 84231025843afb62d818bf4f21612051。“2022JueJin” 就是我们需要加密的明文数据,得到的结果需要转为字符串输出,不然会是一个对象,而不是 16 进制字面值显示的 32 位字符串(由 0 - 9 和 a - f 组成)。

  • WordArray

传入CryptoJS.MD5() 的参数除了字符串外,还可以是 CryptoJS 定义的一种叫做 WordArray 的数据类型。

const wordArray = CryptoJS.enc.Utf8.parse('2022JueJin')
CryptoJS.MD5(wordArray).toString()

上面这段代码将 utf8 字符串先转成了 WordArray 对象,再将其传给 CryptoJS.MD5(),最终得到的结果如果打印输出的话也是 84231025843afb62d818bf4f21612051。

enc 可以看成是 Encode(编码) 的缩写。Utf8 还能换成 Latin1HexBase64 等。如果想将一个 WordArray 对象转换回 utf8 字符,可以执行

CryptoJS.enc.Utf8.stringify(wordArray)

一般情况下,消息摘要算法得到的结果都是以 16 进制字面值表示,如果想要得到 Base64,可以将加密结果通过 CryptoJS.enc.Base64.stringify() 转换:

const base64 = CryptoJS.enc.Base64.stringify(CryptoJS.MD5('2022JueJin'))
console.log(base64) // hCMQJYQ6+2LYGL9PIWEgUQ==

algo

如果需要生成 MD5 的数据是个大文件,一般我们可以把大文件分为多段。采用下面的方式,先使用 CryptoJS.algo.MD5.create()创建一个对象,命名为 hasher。然后将数据一段段的传入 hasher.update() 处理:

const hasher = CryptoJS.algo.MD5.create()
hasher.update('2022')
hasher.update('JueJin')
const hash = hasher.finalize()
console.log(hash.toString())

最后调用 hasher.finalize() 表示传输完成,finalize() 里也可以传入数据。打印得到的结果也是 84231025843afb62d818bf4f21612051。如果想清除之前的 update,可以调用 hasher.reset()

上面介绍的单向散列函数严格来说并不是加密算法,更多是用于签名。项目中需要进行加密的时候,最好采用下面介绍的加密算法。它们的加密和解密过程是可逆的,分为对称加密和非对称加密。

对称加密算法 AES

所谓对称,指的是加密和解密使用的是相同的秘钥,常见的有 DES、3DES 、RC4、RC5、RC6 和 AES。下面以我在公司最近的项目中使用的 AES 为重点进行介绍。

英文全称为 Advanced Encryption Standard,即高级加密标准的意思。它的推出,用于取代已经被证明不安全的 DES 算法。AES 属于分组加密算法,因为它会把传入的明文数据以 128 bit 为一组分别处理。其秘钥长度则可以是 128、192 和 256 bit。AES 或者说对称加密算法的优点是速度快,缺点就是不安全,因为网站上的代码和秘钥都是明文,别人只要得到了加密结果再结合秘钥就能得到加密的数据了。

使用 crypto-js 进行 AES 加密

加密

我们将 “JueJin2022” 通过 AES 加密,得到的将是一个对象,我们需要通过 toString() 将其转成字符串输出,最终得到的是一个以 base64 编码的 “5yOOaUK1NSxVcRc8TA1fZw==”,代码如下:

const message = CryptoJS.enc.Utf8.parse('JueJin2022')
const secretPassphrase = CryptoJS.enc.Utf8.parse('0123456789asdfgh')
const iv = CryptoJS.enc.Utf8.parse('0123456789asdfgh')
const encrypted = CryptoJS.AES.encrypt(message, secretPassphrase, {
  mode: CryptoJS.mode.CBC,
  paddding: CryptoJS.pad.Pkcs7,
  iv
}).toString()
console.log(encrypted)

CryptoJS.AES.encrypt() 可以传入 3 个参数: 第 1 个为需要加密的明文; 第 2 个是秘钥,长度可以是 128、192 或 256 bit; 第 3 个为一个配置对象,可以添加一些配置。常见的配置属性有:

  • mode:加密模式。默认为 CBC,还支持且常用的是 ECB。CBC 模式需要偏移向量 iv,而 ECB 不需要。
  • paddding:填充方式。默认为 Pkcs7;
  • iv:偏移向量 ;

注意,明文、秘钥和偏移向量一般先用诸如 CryptoJS.enc.Utf8.parse() 转成 WordArray 对象再传入,这样做得到结果与不转换直接传入是不一样的。

解密

解密的写法和加密差不多,只是把 encrypt 方法名改为 decrypt,然后传入的第 1 个参数由明文替换为密文,最后将之前转换明文的方式传入 toString() 即可:

const secretPassphrase = CryptoJS.enc.Utf8.parse('0123456789asdfgh')
const iv = CryptoJS.enc.Utf8.parse('0123456789asdfgh')
const decrypted = CryptoJS.AES.decrypt(
  '5yOOaUK1NSxVcRc8TA1fZw==',
  secretPassphrase,
  {
    mode: CryptoJS.mode.CBC,
    paddding: CryptoJS.pad.Pkcs7,
    iv: '0123456789asdfgh'
  }
).toString(CryptoJS.enc.Utf8)
console.log(decrypted) // JueJin2022

注:如果之前在加密时没有将明文进行 parse 而是直接传入的,那么在解密时,传入 toString() 的解析方式就是写默认的 CryptoJS.enc.Utf8

非对称加密RSA

所谓的非对称,即加密和解密用的不是同一个秘钥。比如用公钥加密,就要用私钥解密。非对称加密的安全性是要好于对称加密的,但是性能就比较差了。

非对称加密算法中常用的就是 RSA 了。它是由在 MIT 工作的 3 个人于 1977 年提出,RSA 这个名字的由来便是取自他们 3 人的姓氏首字母。我们在访问 github 等远程 git 仓库时,如果是使用 SSH 协议,需要生成一对公私秘钥,就可以使用 RSA 算法。

使用 JSEncrypt 进行 RSA 加密

我们依旧是采用 cdn 方式直接在页面中引入 JSEncrypt 库:


使用的代码非常简单。首先需要 new 一个实例对象出来,然后将通过 openssl 生成的公钥传给实例对象的 setKey 方法,之后只需要把要加密的明文传给实例的 encrypt() 进行加密即可:

const crypt = new JSEncrypt()
crypt.setKey('openssl 生成的公钥')
const text = 'JueJin2022'
const enc = crypt.encrypt(text)
console.log(enc)

生成的密文是一段 base64 格式的 1024 位 RSA 私钥。

使用 JSEncrypt 进行 RSA 解密

解密就是把私钥传给实例的 setKey(),之后把密文传给 decrypt() 进行解密即可:

const crypt = new JSEncrypt()
crypt.setKey('openssl 生成的私钥')
const enc = 密文
const dec = crypt.decrypt(enc)
console.log(dec)

注意,setKey 有 2 个别名: 如果传入的是私钥,可以用 setPrivateKey() 替换 setKey(); 如果传入的是公钥,可以用 setPublicKey() 替换 setKey()

OpenSSL

从上面的内容可知,JSEncrypt 的加解密过程需要用到 OpenSSL 来生成秘钥,OpenSSL 是一个开源的软件,它是对 SSL 协议的实现。能够用于生成证书、证书签名、生成秘钥和加解密等。比如我公司最近的项目有个需求是要在本地开发时,localhost 使用 https 协议,就有用到 openssl。

安装

可以去 slproweb.com/products/Wi… 选择对应版本安装:

前端加密cryptojs与JSEncrypt使实例详解_第2张图片

然后在环境变量中添加配置,例如我把 openssl 安装在了 D:\OpenSSL-Win64,就将 D:\OpenSSL-Win64\bin 添加到 Path 中:

前端加密cryptojs与JSEncrypt使实例详解_第3张图片

生成私钥

我们可以在想要保存秘钥的文件夹启动命令行工具,并输入以下命令生成秘钥文件:

openssl genrsa -out rsa_1024_priv.pem 1024
  • genrsa: 生成 RSA 私有密钥;
  • -out:生成的密钥文件,后面配置的是我们生成的密钥文件的名字,可从中提取公钥;
  • 1024:生成的秘钥长度为 1024 bit;

生成的秘钥文件如下:

前端加密cryptojs与JSEncrypt使实例详解_第4张图片

可以通过 cat rsa_1024_priv.pem 查看秘钥内容,然后复制粘贴给上面的 crypt.setKey()

注意:秘钥必须写成一行以 -----BEGIN PRIVATE KEY----- 开头,以 -----END PRIVATE KEY----- 结尾。像下面这样:

crypt.setKey(  '-----BEGIN PRIVATE KEY-----MIICdFCQBj...中间省略...D3t4NbK1bqMA=-----END PRIVATE KEY-----')

生成公钥

生成上面的私钥对应的公钥的命令为

openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem
  • rsa:处理 RSA 密钥的格式转换等问题;
  • -pubout:指定输出文件是公钥;
  • -in:输入文件,也就是我们上面生成的私钥文件;
  • -out:输出文件,也就是是我们要生成的公钥文件;

查看同样是使用 cat 命令。

以上就是前端加密cryptojs与JSEncrypt使实例详解的详细内容,更多关于前端加密cryptojs JSEncrypt的资料请关注脚本之家其它相关文章!

你可能感兴趣的:(前端加密cryptojs与JSEncrypt使实例详解)