密码学实验二

European ePassport

python 复制代码
from hashlib import sha1
import codecs
import base64
from Crypto.Cipher import AES
import binascii
 
# 求未知数,是到期日的校验位,根据校验规则计算
def Unknown_Number() -> int:
    Unknown_Number = 0
    number = "111116"  # 预设的数字
    weight = "731"  # 权重
    for i in range(0, len(number)):
        Unknown_Number += int(number[i]) * int(weight[i % 3])
    return Unknown_Number % 10  # 返回校验位
 
# 计算k_seed
def cal_Kseed() -> str:
    MRZ_information = "12345678<811101821111167"  # 护照信息
    H_information = sha1(MRZ_information.encode()).hexdigest()  # 使用SHA1进行哈希
    K_seed = H_information[0:32]  # 取哈希值的前32位作为K_seed
    return K_seed
 
def cal_Ka_Kb(K_seed):
    c = "00000001"
    d = K_seed + c
    H_d = sha1(codecs.decode(d, "hex")).hexdigest()  # 对K_seed进行哈希
    ka = H_d[0:16]  # 取前16位作为ka
    kb = H_d[16:32]  # 取后16位作为kb
    return ka, kb
 
# 对Ka和Kb分别进行奇偶校验,得到新的k1和k2
def Parity_Check(x):
    k_list = []
    a = bin(int(x, 16))[2:]  # 将16进制转为2进制
    for i in range(0, len(a), 8):
        # 7位一组分块,计算一个校验位,使1的个数为偶数
        if (a[i:i + 7].count("1")) % 2 == 0:
            k_list.append(a[i:i + 7])
            k_list.append('1')
        else:
            k_list.append(a[i:i + 7])
            k_list.append('0')
    k = hex(int(''.join(k_list), 2))  # 将2进制转为16进制
    return k
 
if __name__ == "__main__":
    K_seed = cal_Kseed()  # 计算K_seed
    ka, kb = cal_Ka_Kb(K_seed)  # 计算ka和kb
    k_1 = Parity_Check(ka)  # 对ka进行奇偶校验
    k_2 = Parity_Check(kb)  # 对kb进行奇偶校验
    key = k_1[2:] + k_2[2:]  # 合并k_1和k_2作为最终的密钥
    print(key)  # 输出密钥
 
    # 待解密的密文
    ciphertext = base64.b64decode(
        "9MgYwmuPrjiecPMx61O6zIuy3MtIXQQ0E59T3xB6u0Gyf1gYs2i3K9Jxaa0zj4gTMazJuApwd6+jdyeI5iGHvhQyDHGVlAuYTgJrbFDrfB22Fpil2NfNnWFBTXyf7SDI")
    IV = '0' * 32  # 初始化向量
 
    # 使用AES进行解密
    m = AES.new(binascii.unhexlify(key), AES.MODE_CBC, binascii.unhexlify(IV)).decrypt(ciphertext)
print(m)  # 输出解密后的明文

Byte-at-a-time ECB decryption (Harder)

python 复制代码
import base64
import os
from random import randint
from Crypto.Cipher import AES

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
backend = default_backend()

from math import ceil
def split_bytes_in_blocks(x, blocksize):
    nb_blocks = ceil(len(x)/blocksize)
    return [x[blocksize*i:blocksize*(i+1)] for i in range(nb_blocks)]

def pkcs7_padding(message, block_size):
    padding_length = block_size - ( len(message) % block_size )
    if padding_length == 0:
        padding_length = block_size
    padding = bytes([padding_length]) * padding_length
    return message + padding

def pkcs7_strip(data):
    padding_length = data[-1]
    return data[:- padding_length]

def encrypt_aes_128_ecb(msg, key):
    padded_msg = pkcs7_padding(msg, block_size=16)
    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
    encryptor = cipher.encryptor()
    return encryptor.update(padded_msg) + encryptor.finalize()

def decrypt_aes_128_ecb(ctxt, key):
    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
    decryptor = cipher.decryptor()
    decrypted_data =  decryptor.update(ctxt) + decryptor.finalize()
    message = pkcs7_strip(decrypted_data)
    return message

# You are not suppose to see this
class Oracle:
    def __init__(self):
        self.key = 'Mambo NumberFive'.encode()
        self.prefix = 'PREF'.encode()
        self.target = base64.b64decode( #You are suppose to break this
            "Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK"
        )
    def encrypt(self, message):
        return encrypt_aes_128_ecb(
            self.prefix + message + self.target,
            self.key
        )

def findBlockSize():
    initialLength = len(Oracle().encrypt(b''))
    i = 0
    while 1: # Feed identical bytes of your-string to the function 1 at a time until you get the block length
        #You will also need to determine here the size of fixed prefix + target + pad
        #And the minimum size of the plaintext to make a new block
        length = len(Oracle().encrypt(b'X'*i))
        if length > initialLength:
            minimumSizeToAlighPlaintext = i+1
            blockSize = length - initialLength
            sizeOfTheFixedPrefixPlusTarget = initialLength
            break
        i+=1
    return blockSize, sizeOfTheFixedPrefixPlusTarget, minimumSizeToAlighPlaintext

def findPrefixSize(block_size):
    previous_blocks = None
    #Find the situation where prefix_size + padding_size - 1 = block_size
    ### Use split_bytes_in_blocks to get blocks of size(block_size)
    i = 0
    diff_idx = 0

    previous_blocks = split_bytes_in_blocks(Oracle().encrypt(b''), block_size)
    cmp_blocks = split_bytes_in_blocks(Oracle().encrypt(b'X'), block_size)
    for i in range(len(previous_blocks)):
        if previous_blocks[i] != cmp_blocks[i]:
            diff_idx = i
            break
    
    i = 1
    while 1:
        # len(R)+i = blockSize
        new_blocks = split_bytes_in_blocks(Oracle().encrypt(b'X'*i), block_size)
        if previous_blocks[diff_idx] == new_blocks[diff_idx]:
            prefix_size = blockSize - i + 1 + diff_idx * block_size
            break
        i+=1
        previous_blocks = new_blocks
    return prefix_size

def recoverOneByteAtATime(blockSize, prefixSize, targetSize):
    know_target_bytes = b""
    for _ in range(targetSize):
        # r+p+k+1 = 0 mod B
        r = prefixSize
        k = len(know_target_bytes)

        padding_length = (-k-1-r) % blockSize
        padding = b"X" * padding_length

        # target block plaintext contains only known characters except its last character
        ct_blocks = split_bytes_in_blocks(Oracle().encrypt(padding), blockSize)
        hh = split_bytes_in_blocks(Oracle().encrypt(padding+b'R'), blockSize)[0]

        # trying every possibility for the last character
        cmp_idx = (prefixSize+padding_length+len(know_target_bytes))//blockSize
        for cc in range(256):
            cc = chr(cc).encode()
            if split_bytes_in_blocks(Oracle().encrypt(padding+know_target_bytes+cc), blockSize)[cmp_idx] == ct_blocks[cmp_idx]:
                know_target_bytes += cc
                break

    print(know_target_bytes.decode())

#Find block size, prefix size, and length of plaintext size to allign blocks
blockSize, sizeOfTheFixedPrefixPlusTarget, minimumSizeToAlighPlaintext = findBlockSize();

print("Block size: ", blockSize)
print("Size of the fixed prefix + target: ", sizeOfTheFixedPrefixPlusTarget)
print("Minimum size to allign plaintext: ", minimumSizeToAlighPlaintext)

#Find size of the prefix
prefixSize = findPrefixSize(blockSize)
print("Prefix size: ", findPrefixSize(blockSize))

#Size of the target
targetSize = sizeOfTheFixedPrefixPlusTarget - minimumSizeToAlighPlaintext - prefixSize
print("Target size: ", targetSize)

print('*'*20+"Plaintext"+'*'*20+"\n")
recoverOneByteAtATime(blockSize, prefixSize, targetSize)

CBC bit flipping attacks

python 复制代码
import os
import random
from Crypto.Cipher import AES

# 补充AES_CBC模块所需的核心函数
def PKCS7_pad(plaintext: bytes, block_size: int) -> bytes:
    """PKCS#7填充:将明文填充至块大小的整数倍"""
    padding_length = block_size - (len(plaintext) % block_size)
    if padding_length == 0:
        padding_length = block_size
    return plaintext + bytes([padding_length]) * padding_length

def PKCS7_unpad(padded_data: bytes) -> bytes:
    """移除PKCS#7填充"""
    padding_length = padded_data[-1]
    if padding_length < 1 or padding_length > len(padded_data):
        raise ValueError("无效的填充")
    return padded_data[:-padding_length]

def AES_CBC_encrypt(plaintext: bytes, iv: bytes, key: bytes) -> bytes:
    """AES-CBC模式加密"""
    cipher = AES.new(key, AES.MODE_CBC, iv)
    return cipher.encrypt(plaintext)

def AES_CBC_decrypt(ciphertext: bytes, iv: bytes, key: bytes) -> bytes:
    """AES-CBC模式解密"""
    cipher = AES.new(key, AES.MODE_CBC, iv)
    return cipher.decrypt(ciphertext)

# 你的原有代码
prepend_string = "comment1=cooking%20MCs;userdata="
append_string = ";comment2=%20like%20a%20pound%20of%20bacon"
parameter = b";admin=true;"

keysize = 16
random_key = os.urandom(keysize)
IV = os.urandom(keysize)

def encryptor(text: bytes, IV: bytes, key: bytes) -> bytes:
    # 将给定的字符串添加到自定义文本中,并通过AES_CBC模式进行加密
    plaintext = (prepend_string.encode() + text + append_string.encode()).replace(b';', b'";"').replace(b'=', b'"="')
    ciphertext = AES_CBC_encrypt(PKCS7_pad(plaintext, len(key)), IV, key)
    return ciphertext

def decryptor(byte_string: bytes, IV: bytes, key: bytes) -> bool:
    # 通过AES_CBC模式解密给定的密文并检查admin是否设置为true
    decrypted_string = PKCS7_unpad(AES_CBC_decrypt(byte_string, IV, key))
    return b";admin=true;" in decrypted_string

def CBC_bit_flipping(parameter: bytes, keysize: int, encryptor: callable) -> bytes:
    # 计算前缀长度
    cipher_length = len(encryptor(b'', IV, random_key))
    prefix_length = len(os.path.commonprefix([encryptor(b'AAAA', IV, random_key), encryptor(b'', IV, random_key)]))
    print("Prefix length: ", prefix_length)

    # 查找前缀占用的完整块数量
    random_blocks = 0
    for i in range(int(cipher_length / keysize)):
        if prefix_length < i * keysize:
            random_blocks = i
            break
    print("Random blocks: ", random_blocks)

    # 计算所需的填充字节数(使输入文本对齐到块边界)
    base_cipher = encryptor(b'', IV, random_key)
    padding = 0
    for i in range(1, keysize):
        new_cipher = encryptor(b'A' * i, IV, random_key)
        new_prefix_length = len(os.path.commonprefix([base_cipher, new_cipher]))
        if new_prefix_length > prefix_length:
            padding = i - 1
            break
        base_cipher = new_cipher
    print("Number of bytes of padding required: ", padding)

    # 构造输入文本并执行位翻转攻击
    input_text = b'A' * padding + b"heytheremama"  # 填充+占位文本
    ciphertext = encryptor(input_text, IV, random_key)
    
    # 计算需要翻转的密文位(核心攻击逻辑)
    modified_bytes = b""
    for i in range(len(parameter)):
        # 位翻转公式:原始密文位 ^ 原始明文位 ^ 目标明文位
        pos = (random_blocks - 1) * keysize + i
        original_cipher_byte = ciphertext[pos]
        original_plaintext_byte = input_text[padding + i]
        target_plaintext_byte = parameter[i]
        modified_byte = original_cipher_byte ^ original_plaintext_byte ^ target_plaintext_byte
        modified_bytes += modified_byte.to_bytes(1, "big")
    
    # 构造修改后的密文
    modified_ciphertext = (
        ciphertext[:(random_blocks - 1) * keysize] +
        modified_bytes +
        ciphertext[(random_blocks - 1) * keysize + len(modified_bytes):]
    )
    return modified_ciphertext

# 执行攻击并验证结果
modified_ciphertext = CBC_bit_flipping(parameter, keysize, encryptor)
decrypted = PKCS7_unpad(AES_CBC_decrypt(modified_ciphertext, IV, random_key))
print("解密结果:", decrypted)
print("是否包含管理员权限:", decryptor(modified_ciphertext, IV, random_key))
相关推荐
Blossom.1188 小时前
把AI“编”进草垫:1KB决策树让宠物垫自己报「如厕记录」
java·人工智能·python·算法·决策树·机器学习·宠物
寂静山林8 小时前
UVa 10989 Bomb Divide and Conquer
算法
兮山与9 小时前
算法23.0
算法
共享家95279 小时前
数独系列算法
算法·深度优先
liebe1*110 小时前
C语言程序代码(四)
c语言·数据结构·算法
进击的圆儿10 小时前
递归专题4 - 网格DFS与回溯
数据结构·算法·递归回溯
程序猿202310 小时前
Python每日一练---第一天:买卖股票的最佳时机
算法
夏鹏今天学习了吗11 小时前
【LeetCode热题100(56/100)】组合总和
算法·leetcode·职场和发展
ZPC821011 小时前
opencv 获取图像中物体的坐标值
人工智能·python·算法·机器人