欧州eパスポートの機械読取領域に埋め込まれたAES鍵の導出と復号

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)

タグ: AES MRZ ePassport SHA1 CBC

Sat, 09 May 2026 23:33:06 +0900 投稿