文章

Cryptopals Challenge 12

题目

Challenge 12 Set 2 - The Cryptopals Crypto Challenges image.png

逐字节 ECB 解密

解密使用AES的ECB模式加密的UNKNOWN_STRING

  • 题目是用一个未知的密钥ECB 模式的加密函数加密一个未知的字符串,要求解密出这个字符串。
  • 题目的步骤是:
    • 用不同长度的相同字节作为输入,推断出密钥长度
    • 根据输出中是否有重复的块,判断是否使用了ECB模式
    • 用一个比密钥长度少一个字节的输入,构建一个字典,找出未知字符串的每个字节

解决方案

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import base64  
from Cryptodome.Cipher import AES  
from Challenge9_ImplementPKCS7padding import PKCS7_pad  
from Challenge10_ImplementCBCmode import PKCS7_trim  
from Challenge11_ECBCBC_detection_oracle import generate_random_bytes, AES_ECB_encrypt  
  
  
ECB_KEY = generate_random_bytes(16)  
  
UNKNOWN_STRING = "Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK"  
b64_msg = UNKNOWN_STRING.encode()  
UNKNOWN_BYTES = base64.b64decode(b64_msg)  
  
  
def encryption_oracle(your_string: bytes):  
    msg = b'The unkonwn string is:\n'  
    plaintext = your_string + msg + UNKNOWN_BYTES  
  
    padded_msg = PKCS7_pad(plaintext, AES.block_size)  
    ciphertext = AES_ECB_encrypt(padded_msg, ECB_KEY)  
    return ciphertext  
  
  
def detect_block_size():  
    feed = b"A"  
    length = 0  
    while True:  
        cipher = encryption_oracle(feed)  
        feed += feed  
        if length and len(cipher) - length > 1:  
            return len(cipher) - length  
        length = len(cipher)  
  
  
# 这里的暴力破解进行了优化,把常见的字符排在前面,遍历的时候先选择常见字符,降低循环次数  
def break_AEC_ECB(block_size: int):  
    common = list(range(ord('a'), ord('z'))) + list(range(ord('A'), ord('Z'))) + [ord(' ')] + list(  
        range(ord('0'), ord('9')))  
    rare = [i for i in range(256) if i not in common]  
    possibilities = bytes(common + rare)  
  
    plaintext = b''  
    check_length = block_size  
  
    while True:  
        prepend = b'A' * (block_size - 1 - (len(plaintext) % block_size))  
        actual = encryption_oracle(prepend)[:check_length]  
  
        found = False  
        for byte in possibilities:  
            value = bytes([byte])  
            your_string = prepend + plaintext + value  
            produced = encryption_oracle(your_string)[:check_length]  
            if actual == produced:  
                plaintext += value  
                found = True  
                break  
        if not found:  
            plaintext = PKCS7_trim(plaintext)  
            print(f"Plaintext: \n{plaintext.decode('ascii')}")  
            return  
  
        if len(plaintext) % block_size == 0:  
            check_length += block_size  
  
  
def main():  
    block_size = detect_block_size()  
    print(f"Block Size is {block_size}")  
  
    break_AEC_ECB(block_size)  
  
  
if __name__ == "__main__":  
    main()

运行结果

1
2
3
4
5
6
7
8
Block Size is 16
Plaintext: 
The unkonwn string is:
Rollin' in my 5.0
With my rag-top down so my hair can blow
The girlies on standby waving just to say hi
Did you stop? No, I just drove by

参考

本文由作者按照 CC BY 4.0 进行授权