w = [7, 3, 1] * 10
letter2num = {chr(ord('A') + i): i+10 for i in range(26)}
letter2num.update({str(i): i for i in range(9)})
letter2num['<'] = 0
# 计算校验和
# 12345678<8<<<1110182<111116?<<<<<<<<<<<<<<<4
s = "12345678<8<<<1110182<111116"
for guess in range(9):
teps = s + str(guess)
checksum = 0
for idx, ch in enumerate(teps):
checksum += letter2num[ch] * w[idx]
checksum = checksum % 10
if checksum == 4:
print(guess)
# 结果是 7
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
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
ka, kb = cal_Ka_Kb(cal_Kseed())
k1, k2 = Parity_Check(ka), Parity_Check(kb)
key = k1[2:] + k2[2:] # 合并k_1和k_2作为最终的密钥
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) # 输出解密后的明文
大意是利用选择明文攻击ECB。
Challenge 14 Set 2 - The Cryptopals Crypto Challenges
该漏洞发生在以下情况:
参考链接
这个网站给出了题目的详细解释和代码框架,可以使人专注于核心部分。
原理:当明文的大小是分组的倍数的时候,pkcs7会添加一个dummy block,其大小就是分组大小。
做法:
Oracle().encrypt(b'')
函数并获取长度来确定初始长度。Oracle().encrypt(b'X'*i)
函数提供延长明文长度,其中 i
是当前循环的迭代次数。i+1
),以及固定前缀加目标的密文大小(即初始长度)。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
原理:
见下例,其中R表示随机前缀,X表示我们将提供给oracle的输入(即选择明文,称 padding),T表示目标。
仍然是不断延长明文,可以发现当 padding 的长度达到一定值时,我们可以发现前面的 block 将不再发生变化。
在发现前的第一次,满足:随机前缀的长度 + padding的长度 = 块长度
RRTT TT
RRXT TTT
RRXX TTTT * first time
RRXX XTTT T *detected that first block did not change*
RRXT TTT
做法:
我这里担心随机前缀的长度大于block的长度,依次首先作了一个判断,但是感觉没有必要。
然后是不断使用延长的padding进行加密,判断第一个block是否不再变化(第一次遇到不变化就可以认为不再变化了)。如果是就可以确定前缀长度。
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
原理:如下例,t 是target的第一个字节,c 是我们暴力枚举的字节,上下两个部分只有这个地方不一样,上方的加密结果是参考,下方暴力枚举 c,会得到 256 种加密结果,第一个 block 和上方加密结果一致的,就是 t。
|Block 1 |Block 2 |Block 3 |
|RRXXXXXXXXXXXXXt|?......?|?......?|
|------known-----|---m1---|---m2---|
|Block 1 |Block 2 |Block 3 |
|RRXXXXXXXXXXXXXc|?......?|?......?|
|------known-----|---m1---|---m2---|
使用等宽字体观看更佳
当得到一个 t 之后,我们将最后一个X替换为所得字符(去掉一个X,添加已得字符),就可以继续暴力破解下一个字节。
|Block 1 |Block 2 |Block 3 |
|RRXXXXXXXXXXXRt|?.....?|?......?|
|------known-----|---m1---|---m2---|
|Block 1 |Block 2 |Block 3 |
|RRXXXXXXXXXXXRc|?.....?|?......?|
|------known-----|---m1---|---m2---|
如果X用完了,就把重现更新X 的长度(祥见参考链接)。
有用的性质:
r + p + k + 1 ≡ 0 m o d B r+p+k+1 \equiv 0 \mod B r+p+k+1≡0modB,其中 r 是随机前缀的长度,p 是 padding 的长度,k 是已知的明文的长度,1 代表了待破解字符 c,B 代表块大小。同余于 0 B 的倍数。
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())
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)
参考:https://thmsdnnr.com/blog/cbc-bitflipping-attacks/
import os
import random
from Crypto.Cipher import AES
from AES_CBC import *
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))
if b";admin=true;" in decrypted_string:
return True
else:
return False
def CBC_bit_flipping(parameter: bytes, keysize: int, encryptor: callable) -> bytes:
# 填充
padding = 0
random_blocks = 0 # 寻找前缀长度
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)
# 查找随机块的数量
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)
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"
string = parameter
modified_string = b""
ciphertext = encryptor(input_text, IV, random_key)
for i in range(len(string)):
modified_string += (ciphertext[i + (random_blocks - 1) * keysize] ^ (input_text[i + padding] ^ string[i])).to_bytes(1, "big")
modified_ciphertext = ciphertext[:(random_blocks - 1) * keysize] + modified_string + ciphertext[(random_blocks - 1) * keysize + len(modified_string):]
return modified_ciphertext
modified_ciphertext = CBC_bit_flipping(parameter, keysize, encryptor)
print(AES_CBC_decrypt(modified_ciphertext, IV, random_key))