0x00前言
本系列文章将带来cryptocals 这套密码学挑战的write-up.不同于通过上课或者看书的方式学习密码学,这些题目来自于现在生活中一些软件系统和密码构造中的缺陷。
本系列每一个题的wp基本是采用如下结构:题目解释、相关知识点讲解、代码实现及解释,运行测试。代码均采用python3实现,代码实现部分是参考国外大佬ricpacca的,结合自己的理解及成文需要进行部分修改。
第二套一共有八关。
第二套题目
![80f7845b28d8c948d6602c739266e082.png](https://i-blog.csdnimg.cn/blog_migrate/c33cb03a767320a8f6f4d1b2c2839872.png)
主要是和分组密码相关的
0x01
- 先来看第9题
![46f2cdf0f2736a2d5d5c62771892e592.png](https://i-blog.csdnimg.cn/blog_migrate/de35399e822eab1636645506c90e70e3.png)
对于分组密码而言,通过会将整数的块的明文转为密文,但是我们一般碰到的消息长度不会是块的整数倍的,这时候就需要将其填充(padding)成块的整数倍大小。最流行的一种填充模式是PKCS#7
题目要求我们用代码实现PKCS#7
在分组加密算法中,进行分组时,划分后最后一组如果不够块大小时,需要填充,常见的填充模式有PKCS#5,PKCS#7。对于PKCS#5而言,明确定义Block的大小是8位,而在PKCS7Padding定义中,对于块的大小是不确定的,可以在1-255之间。
填充字符串由一个字节序列组成,填充的字节都是一个相同的字节,该字节的值就是要填充的字节的个数。
比如要填充7个字节,则需要填充7个0x7
实现一个函数,用于进行PKCS#7填充。如果消息长度等于块大小的话,无需填充,直接返回;否则先计算需要填充的长度,然后将其附加在原消息之后
![e8bf6097d38df80621d0cd70e5b27d92.png](https://i-blog.csdnimg.cn/blog_migrate/b63072d36f42fb75b9692a711842ca16.png)
实现一个函数,用于判断数据是否经过PKCS#7填充。假设是正常实现的填充,将所填充的字符串存入padding,然后根据“填充的字节都是一个相同的字节,该字节的值就是要填充的字节的个数“进行检查。如果检查通过,则是经过PKCS#7填充的。
![3e5190a8184aaf80a351702315d74b6c.png](https://i-blog.csdnimg.cn/blog_migrate/63a217aed972df0e154bd0b07ae874bc.png)
可以对应实现一个函数,实现逆PKCS#7填充,也就是将被填充的数据还原,通过is_pkcs_padded函数检查,如果没被填充过则直接返回,如果被填充过,则直接截取被填充后的字符串的前一部分(即原始数据)返回
![ceac1014c51acb2d41b16f1c151c5bfb.png](https://i-blog.csdnimg.cn/blog_migrate/2e13db390940b84ab3eec8da4fc3e78e.png)
完整代码及执行结果如下
![05416068ba409774a8ae74882ebf4806.png](https://i-blog.csdnimg.cn/blog_migrate/1c654adce9aa440921bfde45c105016e.png)
顺便使用断言测试pacs7_unpad是否可用
![77f1f3a9b80e2699d6b3f79d96c3655b.png](https://i-blog.csdnimg.cn/blog_migrate/eb49214f038157c141c006793c620c92.jpeg)
没有报错,说明正常执行
- 实验推荐
CTFCrypto练习之分组密码
http://hetianlab.com/expc.do?ec=ECID172.19.104.182015011915475800001(通过本实验的学习,你能够了解CTF竞赛中的密码学题型,掌握分组密码以及AES加密算法,学会分组密码ECB模式的攻击方法)
0x02
第10题
![280fc376870a3af3f279929339b6fdfc.png](https://i-blog.csdnimg.cn/blog_migrate/907014e0a9429030b78379a0c1693656.png)
要求我们实现CBC模式,并对给出的文本文件进行解密。并提示我们复用之前写过的EBC代码(不过此时是写加密的函数)和XOR代码。还给出了密钥和IV
这里涉及的知识点是CBC模式
其中文全称是密码分组链接模式(Cipher Block Chaining (CBC))
这种模式是先将明文切分成若干小段,然后每一小段与初始块(IV)或者上一段的密文段进行异或运算后,再与密钥进行加密。如下所示
![53d22446ce44652d7d14752a794a6a60.png](https://i-blog.csdnimg.cn/blog_migrate/ceb85385a99accbb5bcb00d997be0ae7.png)
实现ASES-ECB加密函数。数据加密前常需要PKCS#7填充。
![1df61baa2e83ce91a373dd44541d3f00.png](https://i-blog.csdnimg.cn/blog_migrate/152503188266f9868b2cc957283301b7.jpeg)
异或函数
![189766b2d8f426b1423c76b6c8aab880.png](https://i-blog.csdnimg.cn/blog_migrate/18bd668fde30714965165cfdb81c0ad5.jpeg)
实现AES-CBC加密函数,其接收的参数就是明文、密钥、IV
分块进行处理,分块后先进行填充padding,再进行与IV或上一个密文块进行异或,然后AES-ECB加密(即前面将CBC实现图中的加密器部分),接着处理下一块
![9582c914eb1824e1f2758d2aadadaa78.png](https://i-blog.csdnimg.cn/blog_migrate/93f059147c2d410e493decba8ef3b399.png)
实现AES-CBC解密函数,可以返回填充或未填充的明文。同样需要分块进行,和加密函数倒着来就行,先AES-ECB解密,然后XOR。
![737fc437aacc3a612643397cbf6593b9.png](https://i-blog.csdnimg.cn/blog_migrate/5f9e87b6af9e40a58a225e5f9488ae76.png)
![adc0d5745240ec4598e19eee41ba52a8.png](https://i-blog.csdnimg.cn/blog_migrate/50e2d010845774747ee365104bcd0d2b.png)
完整代码及执行后得到的明文如下
![8a20f81bdebbeee17c04e805f2df4ae0.png](https://i-blog.csdnimg.cn/blog_migrate/dce730ad914e2c8cf0b9b96faa4123db.jpeg)
- 实验推荐
CBC字节翻转攻击
http://hetianlab.com/expc.do?ec=ECIDf328-1dc9-464c-918b-543b4a2d6590
(通过该实验了解CBC模式实现流程、异或运算的高级应用、python中crypto库的使用以及cbc字节翻转攻击的原理与代码实现)
0x03
第11题
![cfaeb9ef9e123fb5d787b8937d193f8f.png](https://i-blog.csdnimg.cn/blog_migrate/4a05b517f8f355a5a54e2156f2016f39.png)
要求写一个函数实现生成随机的16字节AES 密钥
写一个函数根据随机密钥进行加密,在加密前在明文的前后各添加5到10字节随机值,随机选取加密方式为ECB或CBC,如果是CBC,还需要选择随机的IV
要求我们能够写一个函数,能够判断某个被加密的块是使用CBC还是ECB模式
根据填充后明文,随机生成的key,随机决定采用的模式(ECB或CBC)进行加密,如果是CBC的话,其IV也是随机的,所以用Random.new()
该数据填充上5到10字节随机数作为前缀和后缀
![e28e5c06ff65254f7936108330258d02.png](https://i-blog.csdnimg.cn/blog_migrate/780fb518626203c6f80dda7f2ec72391.jpeg)
根据count_aes_ecb_repetitions返回的结果来判断是ECB还是CBC加密模式
![b230b40d11cead31ef7a5affc79fbfaf.png](https://i-blog.csdnimg.cn/blog_migrate/e9e5c16cf20eb0954416ce92d52458a9.png)
我们选择重复的输入数据,这样我们就可以结合ECB的特点来探测其是否为ECB模式,不是ECB则是CBC模式。
![29958c66534e2e33863b1556961947f0.png](https://i-blog.csdnimg.cn/blog_migrate/2999d141aebb615c30d1687861d6f3d7.png)
完整代码及执行断言结果如下
![fdc19c365b85354593a15542d859dae1.png](https://i-blog.csdnimg.cn/blog_migrate/d56a4274097f8d4382ac4de7dc4f9b22.jpeg)
没有报错,说明写的这些函数都能成功执行。
0x04
第12题
![95543d824da549e260676efe9a6fb20f.png](https://i-blog.csdnimg.cn/blog_migrate/96d8357e4b93f2bcdd71fc2148ac291f.jpeg)
该函数使用ECB模式,使用同一个但是未知的key
在加密前,在明文后附加给出的字符串(在附加之前先对该字符串base64解密我们有一个函数可以计算AES-128-ECB(your-string || unknown-string, random-key),而ECB Byte at a time技术可以让我们需要控制your-string,就可以在不知道random-key的情况下得到unkown-string。
基本流程如下:
1.将相同的字符串字节传入函数1,从传入1个字节(A)开始,然后AA,AAA。。。直到找到密文的块的大小
2.测试加密模式是否为ECB
3. 知道块的大小后,设计一个恰好少1字节的输入块(例如,如果块大小为8字节,则输入“ AAAAAAA”)。思考一下oracle函数将在最后一个字节位置放置什么。
4. 通过将不同的字符串输入到oracle中,为每个可能的最后一个字节创建字典;例如“ AAAAAAAA”,“ AAAAAAAB”,“ AAAAAAAC”,记住每个调用的第一个块。
5. 将one-byte-short输入的输出与字典中的一项匹配。 现在,我们就已经发现了unknown-string的第一个字节。
6.针对下一个字节继续重复
这里的原理还是基于ECB模式自身缺陷
假设我们有aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab,16字节分组后如下所示
![ee96a01bc871cecca1d72f7917def7a7.png](https://i-blog.csdnimg.cn/blog_migrate/2bd3d550654a171caca9dc535352e48a.png)
对于前两个块,他们的输入都是16个a,他们对应的输出是一样的。也就是说相同输入会有相同输出,我们就可以根据这一点进行攻击。
举个实际生活中的例子,假设找到一个网站,它的会话cookies是这么生成的:
AES_ECB(USERNAME + SECRET, KEY),我们作为攻击者可以任意改变username,得到不同的密文。作为攻击者,我们想要得到secret。来看看我们是怎么攻击的。
首先我们将15个a作为username,得到密文
![c4d0af1f0d543e9f9062c0e9803f7c7c.png](https://i-blog.csdnimg.cn/blog_migrate/00574587ca9823d4b0b8744b287bd7e2.png)
可知作为一个16字节大小的块,第16字节是来自secret的未知字符
接下来暴力测试第16个字节的可能值,将得到的相应密文与上图的密文对比
![89663d8fc7879256e9a87de83fa618f9.png](https://i-blog.csdnimg.cn/blog_migrate/d27cd26922b6951dcafa3952434fe720.png)
如上所示,当第16字节为c时,生成的密文和之前得到的密文是相同的,所以我们就知道secret的第1个字节是c了。
接下来我们设置username为14个a,则在构造块时,第15字节是secret的第一个字节c,那么第16个字节自然是来自secret的第二个字节,暂时未知,同样可以得到密文
![e050af830f36996977cdeab7bc0aafa4.png](https://i-blog.csdnimg.cn/blog_migrate/d8a2cab06362241afdb95d24275c605e.png)
然后暴力测试第16个字节的可能值
![47b499503f17bf28a3c662c4810a7fad.png](https://i-blog.csdnimg.cn/blog_migrate/9434aa7033896c4a52fafe60be7daea3.png)
第16个字节为o时,密文才是一样的,所以可知secret的第二个字节为o
以此类推,就可以得到secret的明文了
回到题目本身,我们来看看如何实现
填充后使用AES-128-ECB模式加密
![521ebbd4dd8a2b4ac1efbf5bebaf70e8.png](https://i-blog.csdnimg.cn/blog_migrate/12d22a36b2259fc95ad8eedb1fbc7868.jpeg)
该函数用于返回encryption_oracle使用的块密码的块长度。
要找到一个块的长度,我们将加密越来越长的明文,直到输出密文的大小也增加为止。发生这种情况时,我们可以轻松地计算出一个块的长度,其值等于新的密文长度与其初始长度之间的差。
![30796712692b10f2b666e693affa93c6.png](https://i-blog.csdnimg.cn/blog_migrate/239d5a0518e672e64c448f8f6e2731d7.png)
该函数用于获取依次获取unknown-string的下一个字节,这个函数是本题的关键。
首先计算要用作输入的字符数,以使unkown-string的第一个未知的字符置于块的末尾。接着计算我们从伪造的密文和实际密文得到的字节数,进行比较,这个值在后面的密文比较中用于截取数据。然后计算实际的密文。之后是暴力尝试,通过比较密文,来找到待求的字符
![c087593636196b6c47d1c326b6ca2d4e.png](https://i-blog.csdnimg.cn/blog_migrate/8dd76e04cb1884e50e9d6aad7ae00c1b.png)
获取块长度,通过判断密文中是否有重复的字符串来判断是否为ECB模式。然后获取要解密的字符数量,其值等于我们加密空信息时得到的密文长度。该值在最后的循环中会用到,在循环体里,一个字符一个字符地操作。
![288e1d06e6458f3a23bdb754bc8c3b6e.png](https://i-blog.csdnimg.cn/blog_migrate/515191901a6376cc3c66cb959d09e6d3.png)
完整代码及执行结果如下:
![9a7bb02a594b25d705fbdc59a0cc789b.png](https://i-blog.csdnimg.cn/blog_migrate/f4ea587c3b83e3f0c81fb60bd7afc15b.jpeg)
可以看到成功拿到了unknown_string的明文
也可以使用断言测试
![82c167e40197692d98a8a7466a79d873.png](https://i-blog.csdnimg.cn/blog_migrate/6f71fac0bb298b0eef50e6aa62819f3a.jpeg)
- 参考:
1. https://cryptopals.com/sets/1
2. https://github.com/ricpacca/cryptopals
本文系合天智汇原创,转载请注明出处。