MysteryTwister AES Key – encoded in the machine readable zone of a European ePassportSolution

Question Overview

Question link: Level II Challenges | MysteryTwister – The Cipher Contest

Full translation, Ref: https://www.codenong.com/cs109540921/

1. The AES encryption mode is CBC, the initialization vector (IV) is zero, and the padding is 01-00. Furthermore, the corresponding key is in a form such as the Machine Readable Zone (MRZ) on the ID document, which is not entirely complete when used with European e-passports.

2. The goal is to find the clear text of the following base64 encoded message:
9MgYwmuPrjiecPMx61O6zIuy3MtIXQQ0E59T3xB6u0Gyf1gYs2i3K9Jxaa0zj4gTMazJuApwd6 + jdyeI5iGHvhQyDHGVlAuYTgJrbFDrfB22Fpil2NfNnWFBTXyf7SDI
3. For encryption, the key KENC based on the Basic Access Control (BAC) protocol has been generated and applied. For decryption, the following characters have been sent, from which KENC can be derived (the encoding type of these characters is described in [1]):
12345678 <8 <<< 1110182 <111116? <<<<<<<<<<<<<< 4
(That’s the one in the picture)
4. Lost during transfer and highlighted with a ”? ”. It can be restored with the help of [2]. To be able to calculate the key KENC later, an overview of the applied encoding can be found [3], a protocol in [4] and an example in [5].
5. Decode the base64 code before decrypting.

All in all, it is to restore the encrypted plain text based on the string of numbers it gives, and then follow the method used in the reference article.

Problem-solving ideas

1. Find unknown numbers
2. Calculate the key based on the obtained number.
3. Decrypt the ciphertext decoded by key and base64

Code analysis

1. Find unknown numbers

Design ideas:

  • In order to calculate the check digit of the expiration date, I designed a function Unknown_Number.
  • In this function, the preset number number and weight weight are used.
  • Iterate through each number in number and multiply it by the corresponding weight.
  • Finally, the sum of these products is returned modulo 10, which is the unknown number required.
def Unknown_Number() -> int:
    Unknown_Number = 0
    number = "111116" # Default number
    weight = "731" # Weight
    for i in range(0, len(number)):
        Unknown_Number + = int(number[i]) * int(weight[i % 3])
    return Unknown_Number % 10 # Return check digit

2. Calculate the key based on the obtained number

Design ideas:

  • First use a function cal_Kseed to calculate K_seed.
  • This function takes the passport information MRZ_information and performs a SHA1 hash on it.
  • Take the first 32 bits of the hash value as K_seed.
def cal_Kseed() -> str:
    MRZ_information = "12345678<811101821111167" # Passport information
    H_information = sha1(MRZ_information.encode()).hexdigest() # Use SHA1 for hashing
    K_seed = H_information[0:32] # Take the first 32 bits of the hash value as K_seed
    return K_seed
  • Next, use the function cal_Ka_Kb to calculate ka and kb.
  • Concatenates K_seed with a constant c and performs a SHA1 hash on it.
  • Take the first 16 digits of the hash value as ka, and the last 16 digits as kb.
def cal_Ka_Kb(K_seed):
    c = "00000001"
    d = K_seed + c
    H_d = sha1(codecs.decode(d, "hex")).hexdigest() # Hash K_seed
    ka = H_d[0:16] # Take the first 16 digits as ka
    kb = H_d[16:32] # Take the last 16 bits as kb
    return ka, kb
  • Finally, use the function Parity_Check to perform parity check on ka and kb, and get the new k_1 and k_2.
  • Merge k_1 and k_2 as the final key.
def Parity_Check(x):
    k_list = []
    a = bin(int(x, 16))[2:] # Convert hexadecimal to binary
    for i in range(0, len(a), 8):
        # Divide a group of 7 bits into blocks and calculate a check digit to make the number of 1's an even number.
        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)) # Convert binary to hexadecimal
    return k

3. Decrypt the ciphertext decoded by key and base64

Design ideas:

  • First, a given ciphertext is base64 decoded.
  • Set an initialization vector IV.
  • Use the CBC mode of AES and the previously calculated key to decrypt the ciphertext.
  • Finally, the decrypted plaintext is output.
ciphertext = base64.b64decode(
    "9MgYwmuPrjiecPMx61O6zIuy3MtIXQQ0E59T3xB6u0Gyf1gYs2i3K9Jxaa0zj4gTMazJuApwd6 + jdyeI5iGHvhQyDHGVlAuYTgJrbFDrfB22Fpil2NfNnWFBTXyf7SDI")
IV = '0' * 32 # Initialization vector

# Decrypt using AES
m = AES.new(binascii.unhexlify(key), AES.MODE_CBC, binascii.unhexlify(IV)).decrypt(ciphertext)
print(m) # Output the decrypted plain text

Answer

Full code:

from hashlib import sha1
import codecs
import base64
from Crypto.Cipher import AES
import binascii

# Find the unknown number, which is the check digit of the expiration date, calculated according to the check rules
def Unknown_Number() -> int:
    Unknown_Number = 0
    number = "111116" # Default number
    weight = "731" # Weight
    for i in range(0, len(number)):
        Unknown_Number + = int(number[i]) * int(weight[i % 3])
    return Unknown_Number % 10 # Return check digit

# Calculate k_seed
def cal_Kseed() -> str:
    MRZ_information = "12345678<811101821111167" # Passport information
    H_information = sha1(MRZ_information.encode()).hexdigest() # Use SHA1 for hashing
    K_seed = H_information[0:32] # Take the first 32 bits of the hash value as 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() # Hash K_seed
    ka = H_d[0:16] # Take the first 16 digits as ka
    kb = H_d[16:32] # Take the last 16 bits as kb
    return ka, kb

# Perform parity checks on Ka and Kb respectively to obtain new k1 and k2
def Parity_Check(x):
    k_list = []
    a = bin(int(x, 16))[2:] # Convert hexadecimal to binary
    for i in range(0, len(a), 8):
        # Divide a group of 7 bits into blocks and calculate a check digit to make the number of 1's an even number.
        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)) # Convert binary to hexadecimal
    return k

if __name__ == "__main__":
    K_seed = cal_Kseed() # Calculate K_seed
    ka, kb = cal_Ka_Kb(K_seed) # Calculate ka and kb
    k_1 = Parity_Check(ka) # Perform parity check on ka
    k_2 = Parity_Check(kb) # Perform parity check on kb
    key = k_1[2:] + k_2[2:] # Combine k_1 and k_2 as the final key
    print(key) # Output key

    # Cipher text to be decrypted
    ciphertext = base64.b64decode(
        "9MgYwmuPrjiecPMx61O6zIuy3MtIXQQ0E59T3xB6u0Gyf1gYs2i3K9Jxaa0zj4gTMazJuApwd6 + jdyeI5iGHvhQyDHGVlAuYTgJrbFDrfB22Fpil2NfNnWFBTXyf7SDI")
    IV = '0' * 32 # Initialization vector

    # Decrypt using AES
    m = AES.new(binascii.unhexlify(key), AES.MODE_CBC, binascii.unhexlify(IV)).decrypt(ciphertext)
    print(m) # Output the decrypted plain text

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Python entry skill treeHomepageOverview 376,186 people are learning the system