CryptoJS
GitHub: github.com/brix/crypto…
文档:cryptojs.gitbook.io/docs/
中文版:yztldxdzhu.github.io/2019/07/23/…
最近做的项目中需要用到加密,在搜索之后,选用了 CryptoJS 来做 AES 加密,下面是自己在学习的过程中,对几个重要概念的笔记,还有一些自己的理解,如有错误之处,还请各位大佬指正。
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 对象
| 参数 | 类型 |
|---|---|
| key | WordArray |
| iv | WordArray |
| 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 是没有用的,每次生成的密文还是不一样的。
注意:实验发现,用做密钥(key)的字符串,长度必须是4的倍数(iv 不做要求),否则在解密时,得到 WordArray 对象后,无法解析出原内容。
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;
if (typeof jsonStr == 'string') {
jsonObj = JSON.parse(jsonStr);
} else {
jsonObj = 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);
// let jsonObj = JSON.parse(jsonStr);
// let plaintext = CryptoJS.AES.decrypt(jsonObj.ct, kPassphrase, {
// mode: CryptoJS.mode.CFB,
// padding: CryptoJS.pad.AnsiX923,
// });
// plaintext = plaintext.toString(CryptoJS.enc.Utf8);
// console.log(plaintext);
return plaintext;
}
上页的例子中,使用的是字符串密钥,在加密过程中会自动生成真正的密钥和 iv、salt,每次加密出来的密文是不一样的。
test10() {
// 加密
const kPassphrase = "com.";
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 引入即可使用