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))