padding-oracle attack in C language.
问题描述
In this assignment, you must decrypt a challenge ciphertext generated using AES in CBC-mode with PKCS #5 padding. (Note: technically this is PKCS #7 padding,since the block size of AES is 16 bytes. But the padding is done in exactly the same way as PKCS #5 padding.) To do so, you will be given access to a server that will decrypt any ciphertexts you send it (using the same key that was used to generate the challenge ciphertext)…but that will only tell you whether or not decryption results in an error!
要求解密一个CBC工作模式,PKCS #7方式填充,AES加密的挑战密文。为此,你需要访问一个服务器(该服务器能用挑战密钥解密任何你发送的密文),它将返回你发送的密文是否填充正确。
All the files needed for this assignment are available here, including a README file that should explain everything.
此作业所需的所有文件都已在这里给出,包括一个解释一切的README文件。
Note that this assignment requires the ability to perform basic networking. Because we do not assume students necessarily know this, we have provided stub code for doing basic networking in C, Java, Ruby, and Python, but you are welcome to use any language of your choice as long as you are able to write code for basic networking functionality in that language. (Students may feel free to post stub code in other languages for the networking component on the discussion boards.)
注意到此作业需要执行基本网络的功能。因为我们不要求学生必须知道这一点,所以我们提供了在C,Java,Ruby和Python中进行基本网络连接的存根代码,但是欢迎使用任何您选择的语言,只要您能够编写代码该语言的基本网络功能。(学生可以自由地在讨论板上为其他语言的网络组件发布存根代码。)
The first step in this project is to send the challenge ciphertext to the server, and verify that you receive back a “no error” message. Once you can do that, the rest is “just” crypto…
该项目的第一步是将挑战密文发送到服务器,并验证您是否收到“无错误”消息。一旦你可以做到这一点,其余的是“只是”加密…
The plaintext,when converted to ASCII, is readable English text, and so you should be able to tell once you have been successful. Once you have successfully recovered the plaintext (in ASCII).
明文转换为ASCII时,是可读的英文文本,所以一旦成功恢复明文,便可得知。
前置技能
CBC工作模式
CBC模式即密码分组链接模式(Cipher-block chaining)。在该模式中,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。
加密过程:
解密过程:
以上内容参考自https://zh.wikipedia.org/wiki/分组密码工作模式
PKCS #7填充
以整字节填充。每个填充字节的值是用于填充的字节数,即是说,添加N个字节,每个的值都是N。 填充的字节数取决于信息末尾到块边缘的距离。
下例中,块大小为 8 字节,需要填充 4 字节:
... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
以上内容参考自https://zh.wikipedia.org/wiki/填充_(密码学)
padding-oracle attack攻击原理
padding-oracle attack针对CBC工作模式攻击,与具体加密算法无关。
服务器判断Padding是否出错一般从后开始检验明文末尾字节的值,若末尾为0x01
则仅填充了一位,填充末尾为0x03
则末尾三字节均为0x03
。利用服务器的返回值,可以在不知道密钥的情况下得到明文。
例如现在有一个CBC工作模式,PKCS #7方式填充的密文IV||C(分组大小为8字节):
... | 21 A2 DD 04 35 0A BA 58 | CD DD AA DD 34 23 A1 64 |
为了方便描述,用$C[i]$ 表示第$i$ 个分组,$C[i][j]$ 表示第$i$ 个分组第$j$ 个字节,$D(*)$ 表示解密算法。
我们想要解密最后一个分组$C[n]$ ,即求$P[n]$ 。
先求明文最后一个分组的最后一位,即$P[n][6]$ 。
假设此时仅填充一个字节,构造初始向量$IV$ 值为:
| 00 00 00 00 00 00 00 00 |
向服务器发送这两个分组构成的密文$IV||C[n]$ :
| 00 00 00 00 00 00 00 00 | CD DD AA DD 34 23 A1 64 |
要使服务器的返回值为$1$ (即显示Padding正确),那么明文最后一位应该为$0X01$ 。
明文末尾一位即$D(C[n][6]) \otimes IV[6]$ ,服务器返回值为$D(C[n][6]) \otimes IV[6] == 0X01$。
此时,需要遍历$[0,256)$ 修改$IV[6]$ 的值,直到服务器的返回值为$1$ 。
假设此时初始向量$IV$ 为:
| 00 00 00 00 00 00 00 A0 |
那么$D(C[n][6]) \otimes IV[6] = 0X01$ ,于是可以得到$D(C[n][6]) = 0X01 \otimes IV[6]$ ,
即$P[n][6] = D(C[n][6]) \otimes C[n-1][6] = 0X01 \otimes IV[6] \otimes C[n-1][6]$ 。
至此,明文最后一个分组的最后一位已经得到,接下来求明文最后一个分组的倒数第二位。
此时假设填充了两个字节,即明文末尾两位均为$0X02$ 。
由于$D(C[n][6]) \otimes IV[6] = 0X01$ ,所以当$D(C[n][6]) \otimes IV^{‘}[6] = 0X02$ 时,
$IV^{‘}[6] = D(C[n][6]) \otimes 0X02 = 0X01 \otimes IV[6] \otimes 0X02$ .
此时的初始向量$IV^{‘}$ 为:
| 00 00 00 00 00 00 00 A3 |
遍历$[0,256)$ 修改$IV^{‘}[5]$ 的值,直到服务器的返回值为$1$ 。
于是$D(C[n][5]) \otimes IV^{‘}[5] = 0X02$ ,即$D(C[n][5]) = 0X02 \otimes IV^{‘}[5]$ ,
那么$P[n][5] = D(C[n][5]) \otimes C[n-1][5] = 0X02 \otimes IV^{‘}[5] \otimes C[n-1][5]$ 。
明文最后一个分组的倒数第二位也已经得到。
依次类推,由此即可以仅根据密文还原出明文。
以上内容参考自http://www.freebuf.com/articles/web/15504.html
连接服务器并测试
The first step in this project is to send the challenge ciphertext to the server, and verify that you receive back a “no error” message. Once you can do that, the rest is “just” crypto…
题目给出了很多文件,我们需要的只有sample.c
,oracle.h
和oracle.c
。
修改服务器地址和端口
本课程的服务器地址和端口已修改为128.8.130.16
,49101
。
在oracle.c
中修改servaddr.sin_addr.s_addr
和servaddr.sin_port
。
创建makefile文件
在文件夹目录下创建makefile
:
sample: sample.o oracle.o
gcc -o sample oracle.o sample.o; rm sample.o
sample.o: sample.c
gcc -c sample.c
oracle.o: oracle.c oracle.h
gcc -c oracle.c
clean:
rm -rf *.o sample
编译文件并尝试连接服务器
在命令行窗口输入:
make
得到可运行文件sample
。
尝试连接服务器并发送挑战密文进行测试:
./sample challenge-ciphertext.txt
若返回值ret
等于1,则表示连接成功且发送的密文正确填充(挑战密文保证正确填充)。接下来只需要修改sample.c
文件进行padding-oracle attack攻击。
改写sample文件
模拟一下原理中的逻辑:
#include "oracle.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
unsigned char ciphertext[3][16]={
{159,11,19,148,72,65,168,50,178,66,27,158,175,109,152,54},
{129,62,201,217,68,165,200,52,122,124,166,154,163,77,141,192},
{223,112,227,67,196,0,10,42,227,88,116,206,117,230,76,49}
};
unsigned char ctext[32], plaintext[48], xor_iv[16];
void solve(int p) {
int i, j, k, ret;
memset(ctext, 0, sizeof(ctext) );
for (i=0; i<16; ++i) ctext[16+i] = ciphertext[p][i];
for (i=0; i<16; ++i) {
for (j=0; j<i; ++j) ctext[15-j] = xor_iv[15-j] ^ (i+1);
for (j=0; j<256; ++j) {
ctext[15-i] = j;
ret = Oracle_Send(ctext, 2);
if (ret == 1) {
xor_iv[15-i] = j ^ (i+1);
plaintext[16*p+15-i] = xor_iv[15-i] ^ ciphertext[p-1][15-i];
break;
}
}
}
}
int main(int argc, char *argv[]) {
int i, ret;
Oracle_Connect();
for (i=1; i<3; ++i) solve(i);
freopen("plaintext.txt", "w", stdout);
for (i=16; i<48; ++i) printf("%c", plaintext[i]);
fclose(stdout);
Oracle_Disconnect();
}
编译后运行:
./sample
得到明文plaintext.txt
:
Yay! You get an A. =)