程序员视角:怎么用代码读取一本护照芯片?拆解 APDU 交互全流程

发布时间:2026/9/5 7:32:24
程序员视角:怎么用代码读取一本护照芯片?拆解 APDU 交互全流程 作为开发者你有没有好奇过机场自助闸机到底是怎么和护照芯片通信的那枚小小的 NFC 芯片既没有 IP 地址也不跑 HTTP读卡器凭什么能读出里面的姓名、照片和指纹答案是一套叫做APDUApplication Protocol Data Unit的命令协议构建在ISO/IEC 14443非接触式智能卡标准之上。这套协议从 2005 年电子护照普及至今没有变过是 ICAO9303 第 10 部分定义的逻辑数据结构LDS的底层传输载体。本文从开发者视角完整拆解读取一本护照芯片的技术流程并讨论为什么在 HTTP/REST 无处不在的今天护照芯片依然坚持用这套 古老 的协议。物理层PC/SC TCL读取护照的第一步是硬件连接。标准做法是用一台支持PC/SCPersonal Computer/Smart Card规范的 NFC 读卡器通过 USB 连接电脑。操作系统提供 PC/SC 驱动栈Windows 的 Winscard、Linux 的 pcsc-lite、macOS 的 CryptoTokenKit应用层通过统一的 API 访问读卡器。护照芯片和读卡器之间的无线传输协议是TCLType Contactless定义在 ISO/IEC 14443-4 中。它本质上是一个面向块的半双工传输协议负责分片、重传和链路保活。TCL 之上才是 APDU 命令。为什么不用蓝牙或 Wi-Fi因为 13.56MHz NFC 的有效距离只有 2-5 厘米这个物理限制本身就是一道安全屏障 —— 它确保护照芯片只能在持证人主动出示时被读取不可能被远距离隔空扫描。这是技术选型的第一性原理。APDU 命令格式五个字节的极简设计APDU 的命令格式极其简洁表格字段长度说明CLA1 字节指令类标识命令所属的应用或安全上下文INS1 字节指令码具体操作SELECT、READ BINARY 等P11 字节参数 1P21 字节参数 2Lc0/1/3 字节后续数据长度可选Data可变命令数据可选Le0/1/3 字节期望响应最大长度可选响应更简单返回数据 两个字节的状态码SW1 SW2。90 00表示成功6A 82表示文件未找到69 82表示安全状态不满足。这套格式设计于 20 世纪 80 年代至今没有大改。它的优势在于足够简单任何微控制器都能解析足够紧凑在低带宽的 NFC 链路上传输效率极高。第一步选择护照应用芯片上电后读卡器发送的第一条命令是SELECT通过应用标识符AID选择护照应用00 A4 04 00 07 A0 00 00 02 47 10 01 00拆解一下00是 CLAA4是 SELECT 指令04 00表示按 AID 选择07是 AID 长度A0000002471001是 ICAO 全局分配的 eMRTD 应用 AID最后的00是 Le期望返回完整的 FCI 信息。芯片返回90 00表示应用选择成功。这一步确认了这枚芯片确实是一本符合 ICAO9303 标准的电子护照而不是一张公交卡或门禁卡。第二步读取EF.COM摸清芯片能力护照应用下有一个特殊文件EF.COMCommon Data它存储了芯片的版本信息和支持的数据组列表。读取它用READ BINARY命令00 B0 00 00 00B0是 READ BINARY 指令00 00是偏移地址最后的00表示读取最多 256 字节。EF.COM的返回数据里最关键的是一个标签为5C的字段列出了芯片中实际存在的所有数据组标识DG1-DG16。程序据此判断这本护照有没有存指纹DG3、有没有附加个人信息DG11从而决定后续读取策略。第三步建立 BAC 安全通道在读取任何个人数据之前必须先通过BACBasic Access Control建立加密通道。BAC 的核心逻辑是从护照资料页的 MRZ机读码中派生会话密钥没有 MRZ 就无法建立通道。完整流程分三步GET CHALLENGE00 84 00 00 08向芯片请求一个 8 字节随机数 RND.IC本地计算用 MRZ 派生的密钥加密随机数和会话密钥生成认证令牌EXTERNAL AUTHENTICATE00 82 00 00 28 40 字节数据将认证令牌发送给芯片芯片验证后返回会话密钥确认。三步完成后后续所有 APDU 命令和响应都用会话密钥加密3DES-CBC并附带 MAC 校验。这就是为什么 隔空读取护照 在技术上不成立 —— 没有 MRZ连加密通道都建不起来。更新的护照使用PACE替代 BAC流程更复杂MSE SET 多次 GENERAL AUTHENTICATE采用 ECDH 密钥交换和 AES 加密安全性更高但 APDU 的传输框架完全不变。第四步读取 DG1 和 DG2安全通道建立后就可以逐个读取数据组了。每个 DG 对应一个短文件 IDSFIDG1 是01DG2 是02。读取流程是先 SELECT 对应文件再用 READ BINARY 循环读取因为单次 READ BINARY 最多读 256 字节DG2 的面部照片可能有几十 KB。DG1 返回的是 MRZ 的 ASN.1 编码副本DG2 返回的是 JPEG2000 格式的面部图像。数据格式由 ICAO9303 第 10 部分严格定义解析后即可得到结构化的个人信息。第五步验证 SOD 签名最后一步是验证SODDocument Security Object的数字签名。SOD 本质上是一个 PKCS#7 格式的签名数据结构包含了所有 DG 的哈希值由签发国的 DSDocument Signer私钥签名。验证流程用 DS 公钥验证 SOD 签名→重新计算各 DG 的哈希值并与 SOD 中的比对→用 CSCA 公钥验证 DS 证书的有效性→确认 CSCA 证书在 ICAO PKD 目录中且未被撤销。全部通过这本护照的数据完整性和签发者真实性才被确认。技术必要性为什么不用 HTTP读到这里你可能会问为什么不直接在芯片里跑一个 TCP/IP 协议栈用 REST API 读取数据APDU 这套 80 年代的协议是不是该淘汰了答案是在护照这个场景下APDUTCL 是技术上的最优解没有之一。第一资源约束。护照芯片的 CPU 通常是 8 位或 16 位微控制器RAM 只有几 KBROM 几十 KB。跑 TCP/IP 协议栈和 HTTP 服务器根本不现实。APDU 的解析逻辑只需要几十行代码几个字节的状态机就能搞定。第二安全隔离。APDU 是一种极简的命令 - 响应协议没有复杂的解析逻辑攻击面极小。HTTP 协议的解析器尤其是支持 chunked 编码、multipart、压缩的完整实现历来是漏洞重灾区。在安全要求极高的护照芯片上减少协议复杂度就是减少攻击面。第三传输效率。NFC 链路的有效带宽只有几百 kbps而且是半双工。HTTP 的头部开销动辄几百字节在这种链路上是巨大的浪费。APDU 命令最小只有 4 字节响应最小 2 字节每一个字节都用在刀刃上。第四生态成熟。全球有数十亿张智能卡银行卡、SIM 卡、护照、身份证都在使用 APDU 协议读卡器、操作系统驱动、开发库、测试工具形成了完整的生态。更换协议的成本是天文数字而收益微乎其微。开发工具推荐如果你想动手实验以下是常用的开发库Javajavax.smartcardioJDK 内置或jnasmartcardioPythonpyscardPC/SC 封装pyPassport护照读取专用C/Clibpcsclite 自定义 APDU 封装AndroidHostApduService模拟卡NfcAdapter读卡器模式需要注意读取护照芯片需要先通过 BAC/PACE 认证而这需要你光学扫描护照资料页获取 MRZ。合法的实验应该只用自己的护照。结语从 SELECT 应用到 BAC 认证再到读取 DG 数据整个护照读取流程不过十几条 APDU 命令。但每一条命令的设计、每一个字节的含义都经过了 ICAO 和全球各国安全机构近 20 年的打磨。在这个万物互联、协议越来越复杂的时代护照芯片提醒我们最好的协议不是功能最多的而是在特定约束下最可靠、最安全、最高效的。APDU 用四个字节的命令头撑起了全球每年数十亿次的跨境身份验证 —— 这就是技术必要性的力量。