当前位置:   article > 正文

crypto-js AES-CTR 实现密文前缀式局部解密细节 踩坑点_import cryptojs from 'crypto-js';

import cryptojs from 'crypto-js';

项目有需求,长明文经过AES-CTR模式加密后,在解密的时候,密文不能直接得到,每次通过某些方法尝试后,只能得到一块密文(按顺序),所以只能一块一块的拼接解密。在使用crypto-js这个库的时候,发送不能直接实现这种局部解密,踩了个大坑,最后经过调试源码,查看文档,花了大半天时间才解决,在此分享一下解决方案。



1.使用crypto-js 进行aes-ctr加密的流程

直接看代码:

import CryptoJS from 'crypto-js';

//参数定义
const key = CryptoJS.enc.Utf8.parse("hv9BbgGupxMIFjFB8hn4KPX9If5G8yw8");
const iv = CryptoJS.enc.Utf8.parse("eUGFebpHnoBov3Pz");
var msg = CryptoJS.enc.Utf8.parse("atfwus_test_test_____"); // 21bytes

//加密
const encrypted = CryptoJS.AES.encrypt(msg, key, { mode: CryptoJS.mode.CTR, iv: iv});
const cipher = encrypted.toString();
const decrypt = CryptoJS.AES.decrypt(cipher, key, { mode: CryptoJS.mode.CTR, iv: iv});

//解密
const deMsg = decrypt.toString(CryptoJS.enc.Utf8);
console.log(deMsg);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可以发现解密成功。

2.尝试局部解密失败

上面的明文加密后得到了两个16字节的密文块,理论上我只给出第一块明文,也是可以正常解密的。但是尝试后,发现并不可以:

//参数定义
const key = CryptoJS.enc.Utf8.parse("hv9BbgGupxMIFjFB8hn4KPX9If5G8yw8");
const iv = CryptoJS.enc.Utf8.parse("eUGFebpHnoBov3Pz");
var msg = CryptoJS.enc.Utf8.parse("atfwus_test_test_____"); // 21bytes

//加密
const encrypted = CryptoJS.AES.encrypt(msg, key, { mode: CryptoJS.mode.CTR, iv: iv});
var cipher = encrypted.toString();

//解密
var decrypt = CryptoJS.AES.decrypt(cipher, key, { mode: CryptoJS.mode.CTR, iv: iv});
var deMsg = decrypt.toString(CryptoJS.enc.Utf8);
console.log(deMsg);
		
// 尝试局部解密 解密第一个密文块
cipher = CryptoJS.enc.Base64.stringify(CryptoJS.lib.WordArray.create(encrypted.ciphertext.words.slice(0, 4)));
var decrypt = CryptoJS.AES.decrypt(cipher, key, { mode: CryptoJS.mode.CTR, iv: iv});
var deMsg = decrypt.toString(CryptoJS.enc.Utf8);
console.log(deMsg);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以发现解密失败。

3.问题分析:padding

我们知道AES要求明文必须是16字节的整数倍,而上述的msg是21位,能正常加密,说明CryptoJS 有默认的padding策略。

查阅官方文档可以看出,默认使用的是Pkcs7这个策略。

在这里插入图片描述
Pkcs7 是一种常用的填充方式,也是默认的填充方式。它在数据块的末尾添加了几个字节,使得数据块的长度是加密算法所要求的整数倍。这些字节的值等于添加的字节数。特别需要注意的是,就算明文是16字节的整数倍,也会在后面填充16个字节的’\x10’。

这样牺牲了数据长度的做法是为了更为灵活透明的去解包数据,发送端和接收端不需要约定好blockSize,接收端总能通过数据包的最后一个字符得到填充的数据长度。

到这里,我们大概清楚了不能局部解密的原因了:

  • 我们加密和解密的时候并未指定padding方式,默认使用Pkcs7。
  • 而Pkcs7这种方式下,需要得到末尾的填充字符下才能确定填充数据长度。
  • 如果我们只给出第一块密文,那么无法确定填充数据长度。
  • 在crypto-js里面,这样解密的密文就不可用。

4.正解:采用ZeroPadding方式

ZeroPadding 是一种简单的填充方式,它在数据块的末尾添加零或多个字节 0x00,以使得数据块长度为加密算法所要求的整数倍。

只要业务里面不涉及数据末尾零字节的加解密,那么这种填充方式也是可行的,并且可以实现上述的局部解密。

代码如下:

//参数定义
const key = CryptoJS.enc.Utf8.parse("hv9BbgGupxMIFjFB8hn4KPX9If5G8yw8");
const iv = CryptoJS.enc.Utf8.parse("eUGFebpHnoBov3Pz");
var msg = CryptoJS.enc.Utf8.parse("atfwus_test_test_____"); // 21bytes

//加密
const encrypted = CryptoJS.AES.encrypt(msg, key, { mode: CryptoJS.mode.CTR, iv: iv, padding: CryptoJS.pad.ZeroPadding});
var cipher = encrypted.toString();

//解密
var decrypt = CryptoJS.AES.decrypt(cipher, key, { mode: CryptoJS.mode.CTR, iv: iv, padding: CryptoJS.pad.ZeroPadding});
var deMsg = decrypt.toString(CryptoJS.enc.Utf8);
console.log(deMsg);
		
// 尝试局部解密 解密第一个密文块
cipher = CryptoJS.enc.Base64.stringify(CryptoJS.lib.WordArray.create(encrypted.ciphertext.words.slice(0, 4)));
var decrypt = CryptoJS.AES.decrypt(cipher, key, { mode: CryptoJS.mode.CTR, iv: iv, padding: CryptoJS.pad.ZeroPadding});
var deMsg = decrypt.toString(CryptoJS.enc.Utf8);
console.log(deMsg);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以发现,局部解密成功了。


参考

  • crypto-js官方文档:https://cryptojs.gitbook.io/docs/
  • pkcs7填充方式:http://eleaction01.spaces.eepw.com.cn/articles/article/item/212414

ATFWUS 2023-04-10

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/176625
推荐阅读
相关标签
  

闽ICP备14008679号