在开发 CPU 卡时使用 CryptoJS 对 16 进制字符串计算 DES 时的问题及解决方案
背景
在开发 CPU 卡时需要使用 DES 计算过程密钥,网上找到的代码示例大部分是 java 的,尝试使用 CryptoJS 计算但是得到的密文不对,期望是 8 字节但是却得到了 24 字节的结果。
出现问题的主要原因是库本身文档的不完善和对加密相关内容的不了解,而且网上也没有搜到使用 js 对 16 进制字符串计算 des 的示例。
关于对称和非对称加密、DES 相关的知识这里不赘述,可见末尾的参考链接。
期望
尝试使用搜到的代码
运行结果
$ node des2.js
原文: 1234800000000000
密钥: 9955551111555599
密文: oSDP1wMIioLR6IdlSECczyYYEYrA27uX
密文解密: 1234800000000000
结果分析
-
密文可以解密得到原文,说明这段代码本身是没有问题的,只是我使用的方式不对
-
密文不是我期望的 16 进制字符串而是 Base64 字符串,需要修改成 16 进制字符串
-
将加密函数
encryptByDES()
返回值encrypted.toString()
替换成encrypted.ciphertext.toString()
-
将解密函数
decryptByDES()
中的密文CryptoJS.enc.Base64.parse(ciphertext)
替换成CryptoJS.enc.Hex.parse(ciphertext)
-
修改后的结果:
原文: 1234800000000000 密钥: 9955551111555599 密文: a120cfd703088a82d1e8876548409ccf2618118ac0dbbb97 密文解密: 1234800000000000
-
-
查看代码发现使用了
CryptoJS.enc.Utf8.parse
解析密钥字符串,也就是按照 utf8 编码解析,而我的密钥是 16 进制字符串,因此正确的用法应该是CryptoJS.enc.Hex.parse
-
代码中只把密钥字符串根据
utf8
编码解析,原文message
是直接作为CryptoJS.DES.encrypt
的第一个参数,这里猜想 CryptoJS 内部默认是将原文字符串按照utf8
解析后再进行 DES 加密。测试后发现使用CryptoJS.enc.Utf8.parse(message)
替换message
时密文不会有变化,说明前面的猜想是对的,因此这里需要将原文也改成按照 16 进制编码处理-
将加密函数
encryptByDES()
中的原文message
替换成messageHex = CryptoJS.enc.Hex.parse(message)
-
将解密函数
decryptByDES()
中的原文decrypted.toString(CryptoJS.enc.Utf8)
替换成decrypted.toString(CryptoJS.enc.Hex)
-
修改后的结果:
原文: 1234800000000000 密钥: 9955551111555599 密文: d2affb829749c6f6f391dd89b1736b72 密文解密: 1234800000000000
-
-
这时候结果的前 8 bytes(
d2affb829749c6f6
)已经是我要的结果了,但奇怪的是原文明明是 8 byte 但密文却是 16 byte,尝试修改原文后发现结尾的 8 byte 总是f391dd89b1736b72
,对应的原文是0808080808080808
,说明 CryptoJS 自动在原文末尾加上了 8 byte0808080808080808
,因此我也只需要忽略末尾 8 byte 即可得到期望的结果。PS:默认的补全行为
- 在原文字节数不是 8 的倍数时会在末尾补
01
直到原文字节数为 8 的倍数,如 12345678123456 => 1234567812345601 - 原文字节已经是 8 的倍数时会在末尾补
0808080808080808
,如 1234800000000000 => 12348000000000000808080808080808
- 在原文字节数不是 8 的倍数时会在末尾补
错误原因总结
-
编码格式错误:默认是按照的 utf8 编码,但传入 16 进制字符串应该使用 Hex 的编码方式
-
输入 8 bytes 的原文却获得了 24 bytes 的密文:因为按照 utf8 时原文中的一个字符编码成 1 个 byte,长度为 16 的字符串编码成 16 byte,加上自动补全的 080808080808080 即 24 byte
// 按照 UTF8 编码 1234800000000000 => 31323334383030303030303030303030 (16 bytes) // 按照 Hex(16进制) 编码 1234800000000000 => 1234800000000000
最终结果
原文: 1234800000000000
密钥: 9955551111555599
密文: d2affb829749c6f6
密文解密: 1234800000000000
有趣的事情是我找到的这个代码片段是 2013 年写的,而且仅仅一个 gist 便有 40+ 的 star,间接说明了大家都不知道怎么用 CryptoJS
。。。😊