ZipCrypto,也称为 PKZIP Stream Cipher,是一种弱加密手段,在1994年就已经被攻破,该加密方式不能经受已知明文攻击(Known Plaintext Attack,KPA)。
明文攻击主要利用大于 12 字节的一段已知明文数据进行攻击,从而获取整个加密文档的数据。也就是说,如果手里有一个未知密码的压缩包和压缩包内某个文件的一部分明文(不一定非要从头开始,能确定偏移就行),那么就可以通过这种攻击来解开整个压缩包。比如压缩包里有一个常见的 license 文件,或者是某个常用的 dll 库,或者是带有固定头部的文件(比如 xml、exe、png 等容易推导出原始内容的文件),那么就可以运用这种攻击。
Biham和Kocher文中回顾了PKZIP Stream Cipher加密解密过程。
原始论文链接: A Known Plaintext Attack on the PKZIP Stream Cipher
本站链接: A Known Plaintext Attack on the PKZIP Stream Cipher
首先,文件压缩后形成压缩包,PKZIP在压缩包中每个文件头部加上12字节的文件头,用于随机化,也用于在解密时识别错误的密钥,然后该文件头和文件内容一起进行加密。
加密过程如下:
- 首先初始化,key为用户输入的二进制的密钥序列。key0,key1,key2为三个全局变量,在加/解密每个字节时都会用到。小写L表示第L个字节
- 然后就是加文件头,开始加密。Pi代表明文第i字节
解密过程如下:
- 首先初始化,和加密过程初始化完全相同
- 然后解密,Ci代表加密的内容。解密后P1-P12是文件头,仅留作校验,解密后不再需要
附上一段代码,实现PKZIP解密,加密类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import zlib class weak_decrypt: def __init__(self) -> None: self.key_0 = 305419896 self.key_1 = 591751049 self.key_2 = 878082192 self.crc32 = zlib.crc32 self.bytes_c = bytes def update_keys(self,byte): self.key_0 = ~self.crc32(self.bytes_c((byte,)), ~self.key_0) & 0xFFFFFFFF self.key_1 = (self.key_1 + (self.key_0 & 0xFF)) & 0xFFFFFFFF self.key_1 = ((self.key_1 * 134775813) + 1) & 0xFFFFFFFF self.key_2 = ~self.crc32(self.bytes_c((self.key_1 >> 24,)), ~self.key_2) & 0xFFFFFFFF def decrypt(self,chunk): chunk = bytearray(chunk) for i, byte in enumerate(chunk): temp = self.key_2 | 2 byte ^= ((temp * (temp ^ 1)) >> 8) & 0xFF self.update_keys(byte) chunk[i] = byte return bytes(chunk) # usage: # decrypter = weak_decrypt() # cipher_text = b'aaaaa' # password = b'pppp' # for byte in password: # decrypter.update_keys(byte) # plian_text = decrypter.decrypt(cipher_text) |
总结:PKZIP Stream Cipher是一种按照文件顺序、逐个字节进行加密/解密的算法。实际使用中,还需要注意到每遇到一个压缩包内的文件,就会从头进行一遍加密/解密过程。实现一个已知密钥zip archive的解密还需要了解ZIP格式等问题,这里就不再赘述了。