文章目录
前言
本次作业共3个题目,分别为PA2 option、cryptopals set 2、MTC3 AES key
一、PA2 option
1.题目要求
Padding Oracle Attack
通过利用服务器对padding结果的响应对CBC加密模式进行破解(使用PKCS #5填充)
2.解题思路
通过对IV的逐字节尝试进行爆破。第一次IV设置为00000…00xx,从00-ff设置IV的最低字节,直到服务器返回1,可认为此时明文padding为01,便可得到密文经过AES解密后的最后一字节为设置的IV最低字节与01的异或。之后开始爆破密文经过AES解密后的倒数第二字节(使padding为0202),此时IV的最低字节已确定,为密文经过AES解密后的最后一字节与02的异或,开始从00-ff尝试IV的倒数第二字节,直到服务器返回1,之后便可按上述逻辑得到密文经过AES解密后的倒数第二字节…依次下去,最终可得到密文经过AES解密后的所有字节,将其与题目给的IV异或便可得到明文。
3.代码
代码如下:
# -*- coding: utf-8 -*-
from oracle import *
import re
CipherText = '9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31'
div = 32 # AES每一块16字节
BLOCK = len(CipherText) / 32 - 1
CipherText = re.findall('.{' + str(div) + '}', CipherText) # 将密文分为3组 第一组为IV
Oracle_Connect()
M = []
for b in range(BLOCK): # 对 2 组密文分别求解
print '破解密文', str(b + 1)
IV = CipherText[b]
Ivalue = []
iv = '00000000000000000000000000000000' # 初始化 iv
iv = re.findall('.{2}', iv)[::-1]
padding = 1
for l in range(16):
print "穷举Ivalue倒数第", str(l + 1), '字节'
for ll in range(l):
iv[ll] = hex(int(Ivalue[ll], 16) ^ padding)[2:].zfill(2) # 更新 iv
for n in range(256): # 遍历 0x00-0xFF
iv[l] = hex(n)[2:].zfill(2)
# 将iv列表反转并与当前要解密的密文连接起来
data = ''.join(iv[::-1]) + CipherText[b + 1]
# 将字符串data16进制解码 用10进制保存在ctext中
ctext = [(int(data[i:i + 2], 16)) for i in range(0, len(data), 2)]
# 向服务器发送IV和要解密的密文和
rc = Oracle_Send(ctext, 2)
# Padding 正确时, 记录 Ivalue, 结束爆破
if str(rc) == '1':
Ivalue += [hex(n ^ padding)[2:].zfill(2)]
break
print '穷举出的IV为', ''.join(iv[::-1])
print '穷举出的Ivalue为', ''.join(Ivalue[::-1])
print '================================================================'
padding += 1
Ivalue = ''.join(Ivalue[::-1])
# IV 与 Ivalue 异或求密文
m = re.findall('[0-9a-f]+', str(hex(int(IV, 16) ^ int(Ivalue, 16))))[1].decode('hex')
M += [m]
print '密文', str(b + 1), '破解成功'
print 'Ivalue' + str(b + 1), '为:', Ivalue
print '密文' + str(b + 1), '解密后为:', m
print '================================================================'
Oracle_Disconnect()
print '密文破解后为', ''.join(M)
4.运行结果
二、cryptopals set 2
1.题目要求
Challenge14:
该函数使用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字节的输入块。
4. 通过将不同的字符串输入到oracle中,为每个可能的最后一个字节创建字典;例如“ AAAAAAAA”,“ AAAAAAAB”,“ AAAAAAAC”,记住每个调用的第一个块。
5. 将one-byte-short输入的输出与字典中的一项匹配。 现在,我们就已经发现了unknown-string的第一个字节。
6.针对下一个字节继续重复
Challenge15:
要求实现一个函数,可以检测一段明文是否为PKCS #7填充,如果是话则去掉填充,不是的话则报错。
Challenge16:
cbc翻转攻击
题目要求首先生成随机的AES密钥。
写一个函数接收任意输入字符串,加上前缀"comment1=cooking%20MCs;userdata=",加上后缀
“;comment2=%20like%20a%20pound%20of%20bacon”,去掉;和=
然后,该函数将输入填充到16字节AES块长度,并在随机AES密钥下对其进行加密。
写一个函数解密该字符串并查找"; admin = true; "。根据字符串是否存在,返回true或false。如果上一个函数正确实现,则第二个函数会返回false。实际上,可以进行cbc翻转攻击,使其返回true。我们通过修改密文来实现这一点(无需密钥)。
2.解题思路
Challenge14:
1.首先找到块长度、加密模式
2.找到前缀长度
3.一次一个byte解密
Challenge15:
根据PKCS #7填充规则来实现
Challenge16:
CBC翻转攻击技术可以通过修改密文来操纵解密后的明文。其原理就是如果对初始化向量中的任意比特进行反转,则明文分组中相应的比特也会反转,其原因是第一个明文分组会和初始化向量进行异或运算。
3.代码
Challenge14代码如下:
import base64
from Challenge10 import aes_ecb_encrypt
from Challenge9 import pkcs7_unpad
import random
from Crypto import Random
from Challenge12 import count_aes_ecb_repetitions, find_length, ECBOracle
class HarderECBOracle(ECBOracle):
def __init__(self, secret_padding):
super(HarderECBOracle, self).__init__(secret_padding)
self._random_prefix = Random.new().read(random.randint(0, 255))
def encrypt(self, data):
# 加密函数加密的内容就是随机前缀+可控字符串+未知字符串
return aes_ecb_encrypt(self._random_prefix + data + self._secret_padding, self._key)
# 找到target-byte的一个字节。填充length_to_use个a,保证将块的最后一个字符设置为target-byte的第一个字符。
def get_next_byte(prefix_length, block_length, curr_decrypted_message, encryption_oracle):
length_to_use = (block_length - prefix_length - (1 + len(curr_decrypted_message))) % block_length
my_input = b'A' * length_to_use
cracking_length = prefix_length + length_to_use + len(curr_decrypted_message) + 1
real_ciphertext = encryption_oracle.encrypt(my_input)
for i in range(256):
fake_ciphertext = encryption_oracle.encrypt(my_input + curr_decrypted_message + bytes([i]))
if fake_ciphertext[:cracking_length] == real_ciphertext[:cracking_length]:
return bytes([i])
return b''
def has_equal_block(ciphertext, block_length):
for i in range(0, len(ciphertext) - 1, block_length):
if ciphertext[i:i + block_length] == ciphertext[i + block_length:i + 2 * block_length]:
return True
return False
# 首先分别加密空消息和一个字符的消息,得到两个密文。比较这两个密文,
# 第一个不同的块就是前缀结束的块。然后需要精确定位是在前缀是在哪个位置结束的。
def find_prefix_length(encryption_oracle, block_length):
ciphertext1 = encryption_oracle.encrypt(b'')
ciphertext2 = encryption_oracle.encrypt(b'a')
prefix_length = 0
for i in range(0, len(ciphertext2), block_length):
if ciphertext1[i:i + block_length] != ciphertext2[i:i + block_length]:
prefix_length = i
break
# 加密“两个块长度+一个随机增量”长度大小的相同的字节,如果字节数足够了(在密文中找到了两个连续的相同块),我们就可以精确计算前缀在其最后一个块中结束的位置。
# 其在最后一块中结束的位置为块长度-i
for i in range(block_length):
fake_input = bytes([0] * (2 * block_length + i))
ciphertext = encryption_oracle.encrypt(fake_input)
if has_equal_block(ciphertext, block_length):
return prefix_length + block_length - i if i != 0 else prefix_length
raise Exception('The oracle is not using ECB')
def byte_at_a_time_ecb_decryption_harder(encryption_oracle):
block_length = find_length(encryption_oracle)
ciphertext = encryption_oracle.encrypt(bytes([0] * 64))
assert count_aes_ecb_repetitions(ciphertext) > 0
prefix_length = find_prefix_length(encryption_oracle, block_length)
mysterious_text_length = len(encryption_oracle.encrypt(b'')) - prefix_length
secret_padding = b''
for i in range(mysterious_text_length):
secret_padding += get_next_byte(prefix_length, block_length, secret_padding, encryption_oracle)
return secret_padding
def main():
secret_padding = base64.b64decode("Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGF"
"pciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IH"
"RvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK")
print(secret_padding)
oracle = HarderECBOracle(secret_padding)
discovered_secret_padding = byte_at_a_time_ecb_decryption_harder(oracle)
print(pkcs7_unpad(discovered_secret_padding))
if __name__ == '__main__':
main()
Challenge15代码如下:
def is_pkcs7_padded(bin_data):
padding = bin_data[-bin_data[-1]:]
return all(padding[b] == len(padding) for b in range(0, len(padding)))
def pkcs7_unpad(data):
if not is_pkcs7_padded(data):
return data
padding_len = data[len(data) - 1]
return data[:-padding_len]
def main():
assert is_pkcs7_padded(b'ICE ICE BABY\x04\x04\x04\x04') is True
print(pkcs7_unpad(b'ICE ICE BABY\x04\x04\x04\x04'))
assert is_pkcs7_padded(b'ICE ICE BABY\x05\x05\x05\x05') is False
assert is_pkcs7_padded(b'ICE ICE BABY\x01\x02\x03\x04') is False
assert is_pkcs7_padded(b'ICE ICE BABY') is False
if __name__ == '__main__':
main()
Challenge16代码如下:
from Challenge10 import aes_cbc_encrypt, aes_cbc_decrypt
from Crypto import Random
from Crypto.Cipher import AES
class Oracle:
def __init__(self):
self._key = Random.new().read(AES.key_size[0])
self._iv = Random.new().read(AES.block_size)
self._prefix = "comment1=cooking%20MCs;userdata="
self._suffix = ";comment2=%20like%20a%20pound%20of%20bacon"
# 实现加密函数,添加前缀和后缀后,使用AES-128-CBC进行加密
def encrypt(self, data):
data = data.replace(';', '').replace('=', '')
plaintext = (self._prefix + data + self._suffix).encode()
return aes_cbc_encrypt(plaintext, self._key, self._iv)
def decrypt(self, ciphertext):
data = aes_cbc_decrypt(ciphertext, self._key, self._iv)
return data
# 实现解密函数,还会检查解密后的内容中是否有:admin = true;
def decrypt_and_check_admin(self, ciphertext):
data = aes_cbc_decrypt(ciphertext, self._key, self._iv)
return b';admin=true;' in data
# 计算块长度。要找到一个块的长度,我们需要加密越来越长的明文,直到输出密文的大小也增加为止。
# 发生这种情况时,我们可以轻松地计算出块的长度,其值等于新的密文长度与其初始长度之间的差
def find_block_length(encryption_oracle):
my_text = ''
ciphertext = encryption_oracle(my_text)
initial_len = len(ciphertext)
new_len = initial_len
while new_len == initial_len:
my_text += 'A'
ciphertext = encryption_oracle(my_text)
new_len = len(ciphertext)
return new_len - initial_len
#加密两个不同的明文字节,得到两个不同的密文,
# 计算两个密文间相同的长度,赋给common_length,确保其为块长度的整数倍。
def find_prefix_length(encryption_oracle, block_length):
ciphertext_a = encryption_oracle('A')
ciphertext_b = encryption_oracle('B')
common_len = 0
while ciphertext_a[common_len] == ciphertext_b[common_len]:
common_len += 1
common_len = int(common_len / block_length) * block_length
#从1开始将越来越多的相同字节添加到明文中,分别加密,比较两个密文,直到它们有一个额外的相同块为止。
# 如果找到了,这意味着通过添加i个字节,我们可以控制相同的输入(包括前缀)为块大小的整数倍,这样我们就可以得到前缀的长度了。
for i in range(1, block_length + 1):
ciphertext_a = encryption_oracle('A' * i + 'X')
ciphertext_b = encryption_oracle('A' * i + 'Y')
if ciphertext_a[common_len:common_len + block_length] == ciphertext_b[common_len:common_len + block_length]:
return common_len + (block_length - i)
def cbc_bit_flip(encryption_oracle):
#获得块长度
block_length = find_block_length(encryption_oracle.encrypt)
#获得前缀长度
prefix_length = find_prefix_length(encryption_oracle.encrypt, block_length)
#计算需要添加多少字节到前缀,才能使得其长度为块长度整数倍
additional_prefix_bytes = (block_length - (prefix_length % block_length)) % block_length
total_prefix_length = prefix_length + additional_prefix_bytes
plaintext = "?admin?true"
#接着计算要添加多少字节到明文才能使得其长度为块长度整数倍
additional_plaintext_bytes = (block_length - (len(plaintext) % block_length)) % block_length
#然后将明文加长1个块长度(用?填充),对其加密。
final_plaintext = additional_plaintext_bytes * '?' + plaintext
#使用异或的方法,我们可以通过更改明文之前的块的字节来生成所需的字节。
ciphertext = encryption_oracle.encrypt(additional_prefix_bytes * '?' + final_plaintext)
semicolon = ciphertext[total_prefix_length - 11] ^ ord('?') ^ ord(';')
equals = ciphertext[total_prefix_length - 5] ^ ord('?') ^ ord('=')
#最后将伪造的密文片段放在一起,组成完整的密文
forced_ciphertext = ciphertext[:total_prefix_length - 11] + bytes([semicolon]) + \
ciphertext[total_prefix_length - 10: total_prefix_length - 5] + \
bytes([equals]) + ciphertext[total_prefix_length - 4:]
return forced_ciphertext
def main():
encryption_oracle = Oracle()
forced_ciphertext = cbc_bit_flip(encryption_oracle)
print(encryption_oracle.decrypt(forced_ciphertext))
if __name__ == '__main__':
main()
4.运行结果
Challenge14:
Challenge15:
Challenge16:
三、MTC3 AES KEY
1.题目要求
根据护照信息计算出key,将这个key作为AES密钥解密给出的密文
2.解题思路
根据护照官方文档计算出Kseed
利用Kseed计算密钥key
3.代码
代码如下:
import hashlib
from Crypto.Cipher import AES
import base64
#奇偶校验函数
def odd_even_verify(x):
k = []
a = bin(int(x, 16))[2:]
for i in range(0, len(a), 8):
if (a[i:i + 7].count("1")) % 2 == 0:
k.append(a[i:i + 7])
k.append('1')
else:
k.append(a[i:i + 7])
k.append('0')
a1 = hex(int(''.join(k), 2))
return a1[2:]
# 22-27位为到期日,?位置是第28位为校验位,根据参考文件的校验方法计算
a = [7, 3, 1] * 2
b = [1, 1, 1, 1, 1, 6]
c = 0
for i in range(0, 6):
c = c + a[i] * b[i]
d = c % 10
print("?位置的数字:", d)
c_text = '9MgYwmuPrjiecPMx61O6zIuy3MtIXQQ0E59T3xB6u0Gyf1gYs2i3K9Jxaa0zj4gTMazJuA\
pwd6+jdyeI5iGHvhQyDHGVlAuYTgJrbFDrfB22Fpil2NfNnWFBTXyf7SDI'
c_text = base64.b64decode(c_text)
#根据参考文件找出机读区信息
mrz = "12345678<8<<<1110182<1111167<<<<<<<<<<<<<<<4"
v_no = mrz[:9]
v_no_verify = mrz[9]
birthday = mrz[13:19]
birthday_verify = mrz[19]
endday = mrz[21:27]
endday_verify = mrz[27]
message = v_no + v_no_verify + birthday + birthday_verify + endday + endday_verify
print("机读区信息:", message)
#根据参考文件的K_seed计算方法计算机读区sha1,取高16位
k_seed = hashlib.sha1(message.encode()).hexdigest()[:32]
print("K_seed:", k_seed)
#根据参考文件计算D
d = k_seed + '0' * 7 + '1'
print("D:", d)
#根据参考文件计算D的sha1
k = hashlib.sha1(bytes.fromhex(d)).hexdigest()
print("D的SHA1值为:", k)
#根据参考文件以及奇偶校验算出ka,kb
k_a = odd_even_verify(k[:16])
k_b = odd_even_verify(k[16:32])
print("Ka为:", k_a)
print("Kb为:", k_b)
#计算出key
key = k_a + k_b
print("Key为:", key)
#解密明文
IV = '0' * 32
m = AES.new(bytes.fromhex(key), AES.MODE_CBC, bytes.fromhex(IV))
print("解密后明文为:", m.decrypt(c_text).decode())
4.运行结果
总结
通过这次实验,学习了对分组密码CBC、ECB模式的多种攻击,包括Padding Oracle Attack,对加密模式有了更加深刻的理解。