GitHub: https://github.com/brix/crypto-js
文档:https://cryptojs.gitbook.io/docs/
中文版:https://yztldxdzhu.github.io/2019/07/23/cryptojs%E5%B0%8F%E8%AE%B0/
Hash
具体使用请参考官方文档
所有经过哈希算法之后,得到的都是一个 WordArray
对象,调用 toString
转化为字符串时,默认转成16进制的字符串,也可指定字符串的格式。
使用哈希算法时,传入的参数可以是 String
类型,或者是 CryptoJS.lib.WordArray
实例。当传入的是一个 String
时,会自动转换为一个以 Utf8
编码的 WordArray
对象
let md5 = CryptoJS.MD5('12345')
// 等同于
let md5 = CryptoJS.MD5(CryptoJS.enc.Utf8.parse('12345'))
Cipher
解密之后得到的是 WordArray
对象。
加密之后得到的是 CipherParams
对象,可以从中读取 key
, iv
, salt
, ciphertext
,可以调用 toString
方法得到对应的字符串,默认是OpenSSL兼容格式,也可指定字符格式。
加/解密时使用的 key
, iv
, salt
都是 WordArray
对象
Cipher输入
对于明文消息,cipher算法接受字符串或CryptoJS.lib.WordArray
的实例。
对于密钥(key),当您传递字符串时,它被视为密码并用于派生实际密钥(key)和IV。 或者,您可以传递表示实际密钥(key)的WordArray
。如果传递实际密钥(key),则还必须传递实际的IV。(即都传 WordArray 对象)
对于密文,cipher算法接受字符串或CryptoJS.lib.CipherParams
的实例。CipherParams
对象表示一组参数,例如IV
,salt
和原始密文本身。
传递字符串时,它会根据可配置的格式策略自动转换为CipherParams
对象。
个人理解:
加密时,如果密钥(key)直接使用字符串,加密算法会内部根据密钥自动生成实际使用的密钥(WordArray
对象),并生成 iv
和 salt
,而生成 iv
和 salt
又会用到一些随机的算法,这样就导致每次加密出来的密文是不一样的,而且必须保留加密过程中产生的 iv
和 salt
,只有使用它们才能解密。
如果密钥使用 WordArray
对象,那么 iv
也必须是 WordArray
对象,这样加密出来的内容就是固定的,同时,指定 iv
时,加密算法便不再自动生成 iv
和 salt
,所以在指定 iv
时,加密后得到的 CipherParams
对象里没有 salt
(除非在加密是也指定了 salt
)。
另外,如果密钥使用了字符串,也指定了 iv
,此时的 iv
是没有用的,每次生成的密文还是不一样的。
Format
加密之后输出的是一个 CipherParams
对象,其中包含了 key
, iv
, salt
, ciphertext
,要想获取密文,需要取出 ciphertext
并调用 toString
方法,也可以直接在 CipherParams
对象上调用 toString
方法,但他们得到的值是不一样的。
如果我们想格式化输入的密文的话,就需要指定 format
参数,定义一个 format 对象,里面包含两个方法,stringify
和 parse
,
-
stringify
:调用加密算法之后,得到CipherParams
对象,在此对象上调用toString
方法时,会触发format
中的stringify
方法,同时把CipherParams
对象作为参数传入,取出其中的ciphertext
对象(也是一个WordArray
),调用它的toString
方法,同时传入自己需要的编码格式(CryptoJS.enc.Utf8
、CryptoJS.enc.Hex
、CryptoJS.enc.Base64
等),即可得到对应的密文。对于iv
和salt
也是同样的操作。 -
parse
:解密时,当第一个参数是一个字符串时,才会调用到parse
方法,如果使用CipherParams
对象则不会触发,使用parse
的主要目的是为了配合stringify
方法,在parse
方法中,解析stringify
方法产生的字符串,得到对应的ciphertext
、iv
、salt
,创建一个CryptoJS.lib.CipherParams
实例并返回。
对于解密,有两种方式,一种是 decrypt
方法的第一个参数传入字符串,此时会触发 format
中的 parse
方法(如果配置了 format),逻辑见上文;
第二种是直接生成一个 CipherParams
对象,作为第一个参数,这样不会触发parse
方法,但同样需要传入 iv
等参数
例:
// Formatter
let CryptoJSFormat = {
/**
* 加密后得到的 CipherParams 对象,调用 toString 时会调用这个方法
*/
stringify: function(cipherParams) {
console.log('CryptoJSAESFormat stringify ----------- ')
// 加密结束,得到 CipherParams 对象,获取其中需要的数据,进行格式化
var result = {
ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64) // 将密文转换成 Base64 格式字符串
};
if (cipherParams.iv) {
result.iv = cipherParams.iv.toString(); // 默认转换成16进制字符串
}
if (cipherParams.salt) {
result.salt = cipherParams.salt.toString();
}
return JSON.stringify(result);
},
/**
* 解密
* 在解密开始时便调用,将数据解析并生成 CipherParams 对象
*/
parse: function(jsonStr) {
let jsonObj = JSON.parse(jsonStr);
// 获取密文并创建 CipherParams 对象
let cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct), // 将密文字符串转换成 WordArray 对象
})
// 如果有 iv 和 salt ,获取并转换成 WordArray 对象
if (jsonObj.iv) {
cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv); // 16 进制字符串转换 WordArray 对象
}
if (jsonObj.salt) {
cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.salt); // 16 进制字符串转换 WordArray 对象
}
return cipherParams;
}
};
使用:
/**
* AES 加密
* @param plaintext 明文字符串
*/
export const AES_Encrypt = (plaintext) => {
let ciphertext = CryptoJS.AES.encrypt(plaintext, kPassphrase, {
mode: CryptoJS.mode.CFB, // mode 和 padding 的默认值分别为 CBC 和 Pkcs7,加解密时需要保持一致
padding: CryptoJS.pad.AnsiX923,
format: CryptoJSAESFormat
}).toString();
// console.log(ciphertext);
return ciphertext;
}
/**
* AES 解密
* @param jsonStr
*/
export const AES_Decrypt = (jsonStr) => {
let plaintext = CryptoJS.AES.decrypt(jsonStr, kPassphrase, {
mode: CryptoJS.mode.CFB, // mode 和 padding 的默认值分别为 CBC 和 Pkcs7,加解密时需要保持一致
padding: CryptoJS.pad.AnsiX923,
format: CryptoJSAESFormat
}).toString(CryptoJS.enc.Utf8);
return plaintext;
}
上而的例子中,使用的是字符串密钥,在加密过程中会自动生成真正的密钥和 iv
、salt
,每次加密出来的密文是不一样的。
test10() {
// 加密
const kPassphrase = "pass";
const ivStr = '123abc'
let pass = 'superman'
let key = CryptoJS.enc.Utf8.parse(kPassphrase)
let iv = CryptoJS.enc.Utf8.parse(ivStr)
let c = CryptoJS.AES.encrypt(pass, key, {
iv: iv,
}).ciphertext.toString(CryptoJS.enc.Base64)
console.log(c)
// 解密
let cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(c),
})
let result = CryptoJS.AES.decrypt(cipherParams, key, {
iv: iv,
})
console.log(result.toString(CryptoJS.enc.Utf8))
}
key
使用 WordArray
对象,此时 iv
也必须使用 WordArray
对象。
Encode
对于 Encode 方法的使用,如果密钥是一个 Utf8
的字符串,就使用 Utf8
的方法来转化 WordArray
对象,如果是一个 16 进制的字符串,就用 Hex
的方法来转化,总之,使用哪个方法取决于字符串是哪种。
let keyStr = 'test2018'
let ivStr = '1234567890abcdef'
let key = CryptoJS.enc.Utf8.parse(keyStr) // 以 utf8 的格式,转化为 WordArray 对象
let iv = CryptoJS.enc.Hex.parse(ivStr) // 以 16进制 的格式,转化为 WordArray 对象
项目中使用
在小程序中使用时,不能使用 npm
方式,安装和构建npm 都可以正常进行,但是在引入到 .js
文件中使用时,会报错。所以还是直接在 GitHub 上下载 release 的包,将文件引入到小程序项目中。
在 Vue 项目中,直接使用 npm 安装,在文件中 import
引入即可使用
参考:
AES Wiki