LOADING

加载过慢请开启缓存 浏览器默认开启

ePassport BAC 密钥派生与 AES 解密

ePassport BAC 密钥派生与 AES 解密

这篇文章复现 MysteryTwister C3 的一道 ePassport BAC 题。

题目给出:

一段不完整 MRZ
一段 AES-CBC 密文
IV = 16 字节全 0
padding = 01-00 padding

目标是:

1. 恢复 MRZ 中缺失的字符
2. 根据 MRZ 派生 BAC 密钥
3. 用 AES-CBC 解密密文
4. 得到题目的 code word

1. 题目中的 MRZ

题目给出的不完整 MRZ 是:

12345678<8<<<1110182<111116?<<<<<<<<<<<<<<<4

其中 ? 是缺失字符。

MRZ 是 Machine Readable Zone,护照和旅行证件中常见。BAC 会从 MRZ 信息里派生密钥,所以缺失一个字符也会导致最终密钥完全不同。

2. MRZ 字符取值

MRZ 校验位计算时,不同字符有不同数值:

0-9 -> 0-9
A-Z -> 10-35
< -> 0

代码:

def mrz_char_value(ch: str) -> int:
if ch == "<":
return 0
if ch.isdigit():
return int(ch)
if "A" <= ch <= "Z":
return ord(ch) - ord("A") + 10
raise ValueError(f"invalid MRZ character: {ch}")

校验位使用循环权重:

7, 3, 1

计算函数:

def mrz_check_digit(field: str) -> str:
weights = [7, 3, 1]
total = sum(mrz_char_value(ch) * weights[i % 3] for i, ch in enumerate(field))
return str(total % 10)

3. 恢复缺失字符

已知 MRZ 有多个字段自带校验位,例如:

passport number
birth date
expiry date

所以可以枚举 ? 的候选字符,检查三个字段的校验位是否同时正确。

MRZ_ALPHABET = "0123456789<ABCDEFGHIJKLMNOPQRSTUVWXYZ"


def recover_mrz_missing_char(line: str) -> str:
if line.count("?") != 1:
raise ValueError("expected exactly one missing character")

for candidate in MRZ_ALPHABET:
trial = line.replace("?", candidate)
passport_no, passport_cd = trial[0:9], trial[9]
birth, birth_cd = trial[13:19], trial[19]
expiry, expiry_cd = trial[21:27], trial[27]

if (
mrz_check_digit(passport_no) == passport_cd
and mrz_check_digit(birth) == birth_cd
and mrz_check_digit(expiry) == expiry_cd
):
return candidate

raise ValueError("no candidate satisfies check digits")

运行结果恢复:

Recovered missing character: 7

完整 MRZ:

12345678<8<<<1110182<1111167<<<<<<<<<<<<<<<4

4. 生成 MRZ information

BAC 不是直接对整行 MRZ 做 SHA1,而是取几个字段拼接。

本题中的 MRZ information:

12345678<811101821111167

代码:

mrz_info = mrz_line[0:10] + mrz_line[13:20] + mrz_line[21:28]

也就是:

passport number + check digit
birth date + check digit
expiry date + check digit

5. BAC 密钥派生

第一步:

K_seed = SHA1(MRZ_information)[0:16]

代码:

k_seed = hashlib.sha1(mrz_info.encode("ascii")).digest()[:16]

第二步,生成加密密钥材料:

SHA1(K_seed || 00000001)[0:16]

第三步,调整 DES 奇校验位。本题最终使用的是 16 字节密钥材料,但每个字节仍要调整奇校验位:

def adjust_des_parity(data: bytes) -> bytes:
adjusted = bytearray()
for byte in data:
candidate = byte & 0xFE
if candidate.bit_count() % 2 == 0:
candidate |= 1
adjusted.append(candidate)
return bytes(adjusted)

最终结果:

K_seed                    : a095f0fdfe51e6ab3bf5c777302c473e
SHA1(K_seed||00000001) : eb8645d97ff725a998952aa381c53079
K_ENC after parity adjust : ea8645d97ff725a898942aa280c43179
K_MAC after parity adjust : b37a15207fbc4554527c67a15ba72f68

6. AES-CBC 解密

题目说明:

mode = AES-CBC
IV = 00000000000000000000000000000000

所以解密:

ciphertext = base64.b64decode(CIPHERTEXT_B64)
plaintext_padded = aes_cbc_decrypt(ciphertext, k_enc, bytes(16))

注意本题 padding 不是 PKCS#7,而是 01-00 padding

去 padding:

def zero_one_unpad(data: bytes) -> bytes:
index = data.rfind(b"\x01")
if index == -1:
raise ValueError("missing 01 marker")
if any(data[index + 1 :]):
raise ValueError("invalid 01-00 padding")
return data[:index]

7. 真实运行结果

运行脚本:

python work\experiment_02_aes_modes\09_recover_mtc3_epassport_message.py

真实 VSCode 截图:

ePassport BAC 运行截图

输出:

Recovered missing character: 7
MRZ information string : 12345678<811101821111167
K_seed : a095f0fdfe51e6ab3bf5c777302c473e
K_ENC after parity adjust : ea8645d97ff725a898942aa280c43179
Plaintext:
Herzlichen Glueckwunsch. Sie haben die Nuss geknackt. Das Codewort lautet: Kryptographie!

code word 是:

Kryptographie

8. 代码截图与测试

代码运行截图:

ePassport BAC 代码运行截图

单元测试里也检查了:

missing_char == "7"
plaintext contains "Kryptographie"

测试截图:

实验二单元测试截图

9. 常见错误

9.1 MRZ 字段切片错误

MRZ 的位置非常重要。切片错一位,K_seed 就完全不同。建议先打印:

MRZ information string

确认后再派生密钥。

9.2 忘记校验位

缺失字符不是随便猜。它必须让护照号、出生日期、有效期等字段校验位同时成立。

9.3 忘记奇校验位调整

如果直接拿 SHA1 的前 16 字节当 K_ENC,可能会解密失败。题目流程要求做 parity adjustment。

9.4 用错 padding

本题是 01-00 padding,不是 PKCS#7。去填充逻辑不同。

9.5 IV 写错

题目明确 IV 是全 0。如果 IV 不对,第一块明文会错,后续结果也无法正常解释。

10. 小结

这道 ePassport BAC 题串起了几个点:

MRZ 校验位
缺失字符枚举
SHA1 派生 K_seed
K_ENC / K_MAC 派生
DES parity adjustment
AES-CBC 解密
01-00 padding 去除

做这种题时,不要急着上 AES。先把 MRZ 恢复和密钥派生每一步打印出来,确认中间值正确,最后再解密。

参考资料

  • MysteryTwister C3 ePassport BAC challenge
  • ICAO Doc 9303, Machine Readable Travel Documents
  • Basic Access Control 相关资料
  • NIST SP 800-38A, CBC mode