Padding Oracle Attack学习笔记
前言
最近看到Padding Oracle Attack,是对加密算法的CBC模式的破解,很是好奇,找了些资料,动手实现了破解算法。 但是过程有点点曲折,记录下心得体会。
原理
以DES的CBC模式为例,分组加密算法在加解密时,都需要把消息分组,一般为8或16字节。以解密为例:
理解破解算法的几个关键点:
关键点1:padding bytes
padding bytes,即明文不足分组长度时,进行填充的字节。以8字节分组,PKCS5模式为例,当明文为HELLO时,不满8个字节,剩余3个字节的填充字节为0x03,即:
padding byte = padding length = block size - block input length
//填充字节 等于 填充长度 等于 分组长度 - 分组输入长度
因此,填充字节只能是0x01, 0x02…0x08。 0x08表示输入长度为8的倍数字节时,后面也会有8个字节的填充数组。 也就是说,填充字节肯定存在,且范围是1~8。
(其他分组长度类似原理)
关键点2:padding error
当构造的输入错误时,会产生padding error,如java中是BadPaddingException,其他语言可自行尝试。 这点很关键,因为Padding Oracle Attack的核心就是这个,利用padding error来不断进行输入尝试。 编写破解工具时,可能看不到异常,但是一定要能根据返回信息判断是不是产生了padding error,如果不能确定,破解是进行不下去的。
关键点3:算法
与解密示意图对应,我们将分组密文进行 block ciper decryption后得到的字节数组称为Intermdiate bytes,将与Intermediate bytes进行XOR的字节数组称为IV,分组密文用block表示,block[i]表示第i个分组密文, plain block[i]表示第i个分组明文,intermediate[i]表示第i个分组密文的Intermdiate bytes。
ok,让我们捋一捋流程:
-
block[i] decrypt得到 Intermediate bytes, 这个操作跟key(密钥)有关,每个密文分组得到的Intermediate bytes是固定的
-
Intetermedaite bytes 异或 IV 得到明文。注意图中的解密过程,前面分组的密文会与后面一个分组的解密密文异或,最终得到后面一个分组的明文。即对应的block[i]的IV为block[i-1]。因此,解密一定从最后一个分组开始。
-
从最后一个分组开始,用全0数组pseudo block[i-1]代替真实的block[i-1], 通过不断的调整psdudo block[i-][7](最后一个字节),通过判断是否返回padding error来确定填充字节是否正确,即0x01,如果正确,则意味着可以得到明文:
if intermediate[i][7] XOR pseudo block[i-1][7] == 0x01: //得到intermedaite bytes intermediate[i][7] = pseudo block[i-1][7] XOR 0x01 //从而得到分组明文 plain block[i][7] = intermediate[i][7] XOR block[i-1][7]
因为一个字节的范围是从0~255,所以对于一个字节的尝试最多是256次,
-
如果构造数组得到正确的填充字节0x01,则意味着intermediate[i]的最后一个字节确定了,从而可以继续调整,当填充字节为0x02时:
pseudo block[i-1][7] = intermediate[i][7] XOR 0x02
调整pseudo block[i-1][6]的字节值,可以得到block[i][6]的明文字节;以此类推,block[i]就可以全部解密。
-
重复步骤3、4,即可以得到全部分组的明文。
遇到的坑
看完上面的原理,像我这种弱鸡会觉得,“靠,这么简单,我要试一试”。 事实会再次证明你too young。。。
坑1
算法的第3点,为毛要用全0数组充当伪造的数组,而且要从最后一个字节开始尝试,且得到的填充字节就一定是0x01?
抱歉,我也没有确定的答案,我找到的源码和资料都是直接这么搞的,更狠的是原作者的PPT上说的是random bytes(希望是我英语不好,读的不仔细吧)。 但是我有个推测:block[i]密文decrypt操作得到的intermediate[i],字节重复且在0~8之前的概率非常之小。
因此,当从全0数组的最后一个字节开始尝试时,遇到非padding error的情况,就极大概率对应填充字节为0x01。 因为,假设此时正确填充字节为0x03,则意味着block[i]的5、6、7这3个字节对应的intermediate都是0x03。
当然,我觉得就算出现此类极端情况,也是有补救措施的。 比如对于填充字节0x01得出一个错误的pseudo block[i][7],那很可能对于0x02,得不出正确的pseudo block[i][6],这个时候退回去,跳过当前的pseudo block[i][7]值往后尝试;以此类推,也可以推导出正确的结果。
当然这个是推测,需要翻算法和源码验证。
坑2
算法第5点,看似正确,转念一想,对于第一个密文分组,咱是不知道原始IV的啊,怎么破?
抱歉,这个真的无解。。。翻的资料都说“初始IV不重要,一般放在第一个分组,明文传输”。。。
刚开始看算法的时候,纠结了很久,因为推不出第一个分组的明文,才一直觉得自己是没有透彻理解原理,就是因为没有注意到这句话。。。
总结
看安全的材料,感觉搞安全的同学们还是挺NB的,佩服。。。不过,惯例,槽还是要吐的,上面那两个坑,尤其第一个,搜到的材料都没有解释,看来大家都比较忙。。。
附录:
- 《白帽子讲WEB安全》
- 破解算法PPT
- PadBauster源码地址