1. 提示された鍵候補の完成
問題に添付されたドキュメントを参照し、チェックディジットの計算方式を確認する。その仕様に従い、初期鍵の "?" に相当する値を求めることができる。
def calc_checksum(data):
data = list(data)
factors = [7, 3, 1, 7, 3, 1]
total = 0
for idx in range(21, 27):
total += int(data[idx]) * factors[idx - 21]
total %= 10
data[27] = str(total)
return ''.join(data)
2. ドキュメントに記述された \(K_{seed}\) の定義に基づく MRZ 情報とシードの導出

def derive_seed(data):
mrz_info = data[:10] + data[13:20] + data[21:28]
digest = sha1(mrz_info.encode()).hexdigest()
return digest[:32]
3. 付録 5 に従った \(K_{ENC}\) (すなわち鍵)の計算

ここでは暗号化鍵 \(K_{ENC}\) のみが必要であるため、カウンタ c は '00000001' として処理する。
def add_parity_bits(hex_str):
result = []
binary = '{:064b}'.format(int(hex_str, 16))
for i in range(0, len(binary), 8):
chunk = binary[i:i+7]
result.append(chunk)
if chunk.count('1') % 2 == 0:
result.append('1')
else:
result.append('0')
return hex(int(''.join(result), 2))[2:]
def compute_enc_key(seed):
data = seed + '00000001'
hashed = sha1(bytes.fromhex(data)).hexdigest()
left = hashed[:16]
right = hashed[16:32]
return add_parity_bits(left) + add_parity_bits(right)
4. 得られた鍵を用いた暗号文の復号
CBC モードの AES により暗号文を直接復号できる。
def decrypt_ciphertext(cipher_b64, key_hex):
raw = b64decode(cipher_b64)
cipher = AES.new(bytes.fromhex(key_hex), AES.MODE_CBC, bytes.fromhex('0'*32))
return cipher.decrypt(raw).decode()
実行結果

全体のコード
from hashlib import sha1
from base64 import b64decode
from Crypto.Cipher import AES
CIPHER_B64 = '9MgYwmuPrjiecPMx61O6zIuy3MtIXQQ0E59T3xB6u0Gyf1gYs2i3K9Jxaa0zj4gTMazJuApwd6+jdyeI5iGHvhQyDHGVlAuYTgJrbFDrfB22Fpil2NfNnWFBTXyf7SDI'
PASSPORT_CODE = '12345678<8<<<1110182<111116?<<<<<<<<<<<<<<<4'
def calc_checksum(data):
data = list(data)
factors = [7, 3, 1, 7, 3, 1]
total = 0
for idx in range(21, 27):
total = (total + int(data[idx]) * factors[idx - 21]) % 10
data[27] = str(total)
return ''.join(data)
def derive_seed(data):
mrz_info = data[:10] + data[13:20] + data[21:28]
digest = sha1(mrz_info.encode()).hexdigest()
return digest[:32]
def add_parity_bits(hex_str):
result = []
binary = '{:064b}'.format(int(hex_str, 16))
for i in range(0, len(binary), 8):
chunk = binary[i:i+7]
result.append(chunk)
if chunk.count('1') % 2 == 0:
result.append('1')
else:
result.append('0')
return hex(int(''.join(result), 2))[2:]
def compute_enc_key(seed):
data = seed + '00000001'
hashed = sha1(bytes.fromhex(data)).hexdigest()
left = hashed[:16]
right = hashed[16:32]
return add_parity_bits(left) + add_parity_bits(right)
def decrypt_ciphertext(cipher_b64, key_hex):
raw = b64decode(cipher_b64)
cipher = AES.new(bytes.fromhex(key_hex), AES.MODE_CBC, bytes.fromhex('0'*32))
return cipher.decrypt(raw).decode()
if __name__ == '__main__':
complete_code = calc_checksum(PASSPORT_CODE)
seed = derive_seed(complete_code)
enc_key = compute_enc_key(seed)
plaintext = decrypt_ciphertext(CIPHER_B64, enc_key)
print(plaintext)