密码学 从入门到放弃 分组密码
XMan Day2,整理一下bibi师傅的笔记。
分组密码部分。
分组密码【Block Cipher的数学模型是将明文消息编码表示后的数字【简称明文数字,划分成长度为n的组,每组分别在密钥的控制下变换成等长的密文,常见的有DES和AES这两大对称加密算法。
分组密码通常是按固定规模将明文分组,对每组均使用一个固定的加密变换来进行运算。
DES
功能简述
给定一个64位的明文和一个64位的密钥,输出一个64位的密文。这个密文可以用相同的密钥解密。
而64位的秘钥中,只有54位在起作用。剩余的位可以直接丢弃,或者当作奇偶校验位。
算法流程图
秘钥置换详解
1 | def left_rotate(res, offset): |
传入key后,第一轮先经pc1选择置换获得56位秘钥,然后拆分成左右两半。
得到左右两半秘钥后,经左偏移表分别偏移相应位数,拼接,再经pc2选择置换获得48位的子秘钥。
左偏移表长为16位,因此共计16轮,最后得到16组48位的子秘钥。
加密主体
1 | def IP(message): |
在获取子秘钥后,先将明文经IP置换,同秘钥置换中的第一步一样,拆封成左右两半。
然后经过十六轮的运算【主要是看cipher_function函数。
在cipher_function函数中,先将半段长为32位的明文拓展为48位,然后同子秘钥异或。
接下来每6位为一组,分8组,对应8组S盒,取6位中的0和5为纵坐标,1到4位横坐标,取数。
比如第一组是110100的时候,取下标0和5,是10,第2行,取下标1到4,是1010,第10列,取数为9,转二进制是1001。
这样便把48位混淆后的密文转为32位了。
然后再将其经P置换。
如此十六轮运算后将得到的左右密文拼接,再经FP置换,得到最终结果。
解密过程
数据解密的算法与加密算法相同,区别在于扩展后同子秘钥进行按位异或的密钥的使用顺序不同,即解密时将生成的16组子秘钥逆序使用。
1 | def decrypt(plain, key): |
完整代码
1 | # 数值转2进制数组位数扩展 |
一些结论
- IP与FP置换对于安全没有意义,是历史遗留问题,FP是IP的逆置换。
- S盒作为DES算法中唯一的非线性器件,是加密的关键所在,然而,多年来,一直未有人将S盒的设计准则公开。
- Expand拓展目的是在加密数据的过程中制造一些雪崩效应,使用数据块中的1位将在下一步操作中影响更多位,从而产生扩散效果。
例题 2019-CISCN-part_des
1 | Round n part_encode-> 0x92d915250119e12b |
题目名是part_des。
给了一个Round n part_encode,长度为64位,可知是第n轮加密的结果。
给了一个key_map,长度为768位,除16得48,可知是16把48位的子秘钥。
可知,这是DES加密的中间结果,需要对其进行解密。
因此将上述加密过程加以改造即可。
1 | # @Author: south |
得到有意义的字符串为y0ur9Ood。
3DES
相当于是对每个数据块应用三次DES算法。
有3个独立密钥的3DES的密钥安全性为168位,但由于中途相遇攻击【知道明文和密文】,它的有效安全性仅为112位。
加密
密文 = Ek3(Dk2(Ek1(明文)))
不是Ek3(Ek2(Ek1(明文))),是为允许3DES用户通过重复密钥来解密由旧的单个DES用户加密的数据。
即重复秘钥时,3DES降级为DES。
解密
明文 = Dk3(Ek2(Dk1(密文)))
常见出题思路
普通题目:CBC模式攻击、Feistel结构的简单攻击方法。
困难题目:需计算差分、积分、代数、MITM等。
ECB模式
相同的明文分组会被转换为相同的密文分组,一组16个字节,当最后一个明文分组的内容小于分组长度时,需要用一些特定的数据进行填充。
我们可以将其理解为是一个巨大的明文分组→密文分组的对应表,因此ECB模式也称为电子密码本模式。
因此,如果明文中存在多个相同的明文分组,则这些明文分组最终都将被转换为相同的密文分组。这样一来,只要观察一下密文,就可以知道明文存在怎样的重复组合,并可以以此为线索来破译密码,因此ECB模式存在一定风险,且攻击者无需破译密码就能操纵明文【交换密文块来颠倒语意。
CBC模式
CBC模式的加密方式为分组后,第一组和作为偏移量的随机字符串异或后被另一个作为秘钥的随机字符串加密,得到的密文用作下一组明文加密前的异或字符串操作,因此其优于ECB模式的一点是不会被交换密文的方式颠倒语意。
CBC比特翻转攻击
已知明文攻击,可以通过修改密文,来使密文解密出来的特定位置的字符变成我们想要的字符,但会使得前序密文无意义。
只需将上一组对应位置的字符与已知对应位置的明文和目标解密明文分别异或一次即可。
例题
1 | import binascii |
那么根据公式。
1 | ∵ |
因此根据上述公式构造Payload来进行CBC翻转攻击使得admin=1。
1 | import binascii |
可见admin已经成功更改为1,但前序明文无意义了。
例题
Jarvis OJ的一题xbitf。
1 | class Unbuffered(object): |
那么依题意。
1 | token = "session=762fda4eacfb8321;admin=0;checksum=77df89f8da92cbde1baa5d34a9e9e791c763cf14ef63dc3c32ce1e757603de30" |
就好了。
1 | session=762fda4eacfb8321;admin=0;checksum=77df89f8da92cbde1baa5d34a9e9e791c763cf14ef63dc3c32ce1e757603de30 |
CBC选择密文攻击
攻击目的是恢复出偏移量IV,当输入密文可控且有解密后明文返回时可解。
明文每次加密前都会和IV异或,IV每次会更新为上一组的密文。
则满足上述条件时,使得cipher[0]==cipher[1],IV可解,公式如下。
1 | ∵ |
例题
Jarvis OJ的一题xcc。
1 | class Unbuffered(object): |
此时,输入密文可控,且有解密后的明文返回。
1 | from zio import * |
Padding Oracle Attack
满足的攻击条件:
- 加密时采用了PKCS5的填充【填充的数值是填充的字符个数。
- 攻击者可以和服务器进行交互,可以提交密文,服务器会以某种返回信息告知客户端的Padding是否正常。
可在不清楚Key和IV的时候解密任意密文。
例如对Cipher[1]解密,Plain[1] = Decrypt(Cipher[1]) ^ Cipher[0]。
Cipher[1]前拼接一个伪造的Cipher[0],用于当做Cipher[1]的初始向量,发送Cipher解密。
爆破Cipher[1]解密后Plain[0]的最后一位流程如下:
构造Cipher[0]最后一位为X ^ 1,发送并观察Padding的结果是否正确,错误返回1。
Feistel 密码结构
Feistel密码结构是用于分组密码中的一种对称结构。
一组明文分成二组,L和R。
L使用K[i]作为秘钥经过F()加密,再和R异或后作为下一轮的R,R不变作为下一轮的L。
每一轮都是可推理的,且每个的内容均为L ^ R ^ K。
L | R |
---|---|
R | RLK1 |
RLK1 | LK1K2 |
LK1K2 | RK2K3 |
RK2K3 | RLK1K3K4 |
RLK1K3K4 | LK1K2K4K5 |
LK1K2K4K5 | RK2K3K5K6 |
RK2K3K5K6 | RLK1K3K4K6K7 |
R^KY | LRKX |
如果F()函数是线性的,即可进行已知明文攻击。
只要知道一组明密文对,就可以解密所有密文。
例题
Jarvis OJ的一题xfz。
1 | import os |
根据如上加密过程,推得公式如下。
1 | test_K[:27] = test[-27:] ^ K2 ^ K3 ^ K5 ^ K6 |
根据公式写出脚本。
1 | test = "d8dc053d372d0690194553d7ffbed36750703f417bcb989f98b181a07203ec4807e239df7f4dd9f983cc8d5081b8f7673e7b9ea32b51".decode("hex") |
更多高级的攻击方式
- 差分攻击
- 积分攻击
- MITM
- 代数攻击