Cryptography Assignment(Ⅱ)


前言

本次作业共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:
Challenge14
Challenge15:
Challenge15

Challenge16:
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,对加密模式有了更加深刻的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值