MIFARE Ultralight AES安全芯片:低成本应用的AES-128与CMAC实战指南 1. 项目概述与核心价值如果你正在设计一个基于NFC的票务、门禁或者物流追踪系统并且对成本敏感但又不能完全牺牲安全性那么MIFARE Ultralight AES这颗芯片很可能已经进入了你的备选清单。它不像它的“大哥”MIFARE DESFire那样拥有复杂的文件系统和极高的安全等级认证但它在一个非常巧妙的平衡点上在保持MIFARE Ultralight系列低成本、小尺寸、低功耗特性的同时首次引入了基于AES-128的对称加密引擎和CMAC消息认证码机制。这意味着什么简单来说过去的Ultralight芯片其安全主要依赖于“锁字节”这种一次性可编程OTP的物理写保护以及相对简单的通信协议。数据在传输过程中是明文的容易被侦听和篡改。而MIFARE Ultralight AES的出现填补了低成本应用中对通信过程安全和动态数据保护的需求空白。它允许你对存储器的特定区域进行读写权限控制只有通过AES密钥认证后才能访问它支持带安全认证的计数器防止票务次数被恶意回滚它甚至能对传输的每一条指令进行完整性校验CMAC确保命令没有被中途篡改。我经手过不少从传统逻辑加密卡如MIFARE Classic或基础型Ultralight卡升级到带认证功能芯片的项目最大的感触就是安全特性的引入绝不能是简单的功能堆砌而是需要对芯片的安全模型有透彻的理解并将其无缝地融入到你的系统工作流中。否则轻则功能无法实现重则留下严重的安全隐患。本文就将结合官方文档如AN13452和实际调试经验为你拆解MIFARE Ultralight AES的核心安全特性、最佳实践配置以及那些数据手册里不会明说但实际开发中一定会遇到的“坑”。2. 芯片安全架构与核心功能解析要玩转MIFARE Ultralight AES不能只把它当成一个带加密功能的存储器。你需要理解它是一台具有特定状态机和安全策略的微型计算机。它的所有行为都围绕着几个核心的安全状态和配置位展开。2.1 内存组织与访问控制基石MIFARE Ultralight AES的内存以页Page为单位组织每页4字节。对于最常见的144字节用户内存版本其布局是理解一切功能的基础。用户内存与配置区前40页00h-27h是用户可用的数据区其中包含序列号UID、厂商数据、锁字节和OTP区。从28h开始是锁字节2/3/4、配置页以及至关重要的两个AES-128密钥存储区。这个布局设计意味着密钥本身也是存储在EEPROM中的可以通过普通的WRITE命令写入。这带来了极大的灵活性但也引入了第一个关键安全考量密钥注入过程必须在安全环境中进行因为芯片本身不支持加密形式注入密钥密钥在个人化阶段是以明文形式写入的。锁字节机制这是芯片的硬件写保护机制。页02h的锁字节0和1以及用户内存后的锁字节2/3/4共同控制着从OTP区到用户内存区的写保护。每个锁位Lx对应一页将其置1后该页将变为只读。更重要的是块锁位BLx。一旦某个内存区域的块锁位被置1该区域对应的锁位配置就被永久冻结无法再更改。例如锁字节0中的BL_OTP位锁定了OTP区的锁定位配置。实操心得锁字节配置策略很多开发者在个人化完成后会记得去锁用户数据页但常常忽略锁配置页和密钥锁定位。这是一个潜在风险点。官方强烈建议在个人化配置固化后立即设置LOCK_USR_CFG位来锁定配置页防止攻击者通过篡改配置如降低AUTH0值来扩大开放区域来绕过保护。同时如果应用场景允许应在写入密钥后立即设置LOCK_AES_KEY0和LOCK_AES_KEY1位并最终设置BLOCK_LOCK_KEY位来冻结这些密钥锁定位。记住这些锁位都是OTP的设错了就没有回头路所以务必在测试卡上反复验证流程后再对正式卡操作。2.2 双密钥体系与芯片状态切换芯片内置两个独立的AES-128密钥Key0 (DataProtKey)和Key1 (UIDRetrKey)。这不是简单的备份而是对应着芯片完全不同的两个安全状态。Key0 (数据保护密钥)这是主密钥。认证成功后芯片进入AUTHENTICATED状态。在此状态下可以访问受AUTH0和PROT配置所保护的内存区域以及受保护的计数器2。Key1 (UID检索密钥)这是一个专用密钥。认证成功后芯片进入TRACEABLE状态。这个状态的核心目的是在启用了“随机IDRandom ID”功能时用于安全地检索出芯片的真实UID和NXP原厂签名。为什么需要两个密钥这体现了职责分离的安全思想。想象一个公共交通场景Key0用于日常检票验证票的有效性并扣款它应该是分散化的即每张卡的Key0由主密钥和卡UID计算得出各不相同这样即使破解一张卡也无法威胁其他卡。而Key1用于在需要追溯票卡来源如防伪稽查时获取卡的真实身份。Key1应该是非分散化的、统一的并由更高级别的管理机构保管。如果Key1也被分散化了而卡又处于Random ID模式你将没有任何手段能恢复出它的真实UID这张卡就彻底“匿名”了。2.3 核心安全配置详解AUTH0, PROT 与 AUTH_LIM这三个配置共同定义了内存访问的安全策略。AUTH0 (认证起始页)这是一个7位的值00h-7Fh。它定义了从哪一页开始访问需要受到认证保护。例如AUTH0 12h意味着从页18十进制开始的所有页面其访问将受到限制。PROT (保护模式)此位决定是仅写保护还是读写都保护。PROT 0写保护读自由。页地址 AUTH0 的区域写入需要认证但读取不需要。这适用于存储公开信息但需要防篡改的场景如产品生产信息。PROT 1读写均需认证。页地址 AUTH0 的区域无论是读还是写都必须先通过Key0认证。这适用于存储敏感数据的场景如电子钱包余额、个人标识符。配置示例与风险假设你设置AUTH004h,PROT1。那么页00h-03hUID、锁字节、OTP是全局可读的而从页04h开始的所有用户数据读写均需认证。这里有一个极易忽略的陷阱AUTH0的最大值可设为7Fh这远大于实际内存页地址如27h。如果你错误地将AUTH0设为一个大于实际最后页地址的值例如AUTH040h那么PROT1的规则将永远不会被触发因为没有任何页的地址“大于等于40h”导致整个内存处于无保护状态。务必在配置后用未认证的读命令尝试访问受保护区域以验证配置是否生效。AUTH_LIM (认证失败计数器)这是一个至关重要的防暴力破解机制。它限制连续失败的认证尝试次数。一旦失败次数达到设定值如100次即使后续输入正确的密钥认证也会永远失败。这直接对抗针对密钥的旁道攻击和暴力枚举。强烈建议在生产环境中启用此功能并将阈值设置为一个合理的较低值如64次。每次成功的认证会将失败计数器减少1610h这为合法操作中的偶尔失误留出了余地。3. AES-128认证流程与CMAC安全消息传递实战理论配置完毕接下来就是让芯片“活”起来的关键认证与安全通信。这是整个安全链条中最核心的交互部分。3.1 AES认证流程逐步拆解认证是一个两步挑战-响应过程。我们以Key0认证为例假设密钥是全零的默认密钥仅用于示例生产环境绝不可用。步骤1: 发起认证 (Authenticate Part 1)读写器发送命令1A 00认证使用Key0。芯片会生成一个16字节的随机数RndB用Key0加密后连同状态码AFh一起返回给读写器。你收到AF 37 41 42 DA FB 0A B9 71 83 D8 46 EB 7E D3 79 E0其中AF是状态后面16字节是Enc(Key0, RndB)。步骤2: 解密并准备响应 (Authenticate Part 2)解密RndB你用Key0解密收到的16字节得到明文RndB。例如解密后得到RndB 0D 2B BA 17 01 10 98 E9 86 4C 8A A5 19 2A F7 96。生成RndA读写器自己生成一个16字节的随机数RndA。例如RndA 42 BD F7 E0 8E 11 0F 14 B6 D3 32 3D 14 F1 C2 B9。构造RndB‘将RndB循环左移一个字节。RndB 2B BA 17 01 10 98 E9 86 4C 8A A5 19 2A F7 96 0D。加密挑战数据将RndA和RndB‘拼接起来共32字节用Key0加密。得到Enc(Key0, RndA || RndB)。发送第二部分将加密后的数据附加在AFh后发送给芯片。命令为AF [32字节加密数据]。步骤3: 验证与状态切换芯片用Key0解密你发送的数据得到RndA和RndB‘。它会验证你发送的RndB‘是否等于它持有的RndB左移后的结果。同时芯片会将自己的RndA也左移一个字节得到RndA‘并用Key0加密后返回给你状态码00h表示成功。你收到00 A1 76 73 DC C2 0D 27 EE 80 58 4A CC FC 39 E0 A3你用Key0解密后16字节得到RndA BD F7 E0 8E 11 0F 14 B6 D3 32 3D 14 F1 C2 B9 42。验证检查RndA是否等于你生成的RndA循环左移一个字节的结果。如果是则认证成功芯片进入AUTHENTICATED状态。踩坑记录随机数生成与会话密钥推导很多自研读卡器程序在认证失败时问题往往出在随机数生成或会话密钥计算上。务必确保随机数质量RndA必须是密码学安全的真随机数不能使用简单的伪随机序列或固定值。许多MCU的硬件随机数发生器RNG在上电初期熵不足需要等待或混合其他熵源。字节顺序在计算会话向量SV用于生成CMAC会话密钥时文档中提到的RndA[15..14]等表示法是指字节数组的切片且遵循小端序LSB first约定。计算错误将导致后续所有CMAC校验失败。建议将官方示例中的每一步数据都打印出来与自己的计算结果逐字节比对。3.2 CMAC安全消息传递为通信加上“防伪码”认证建立了安全通道但之后的读写操作呢如果没有完整性保护攻击者仍然可以截获、篡改或重放指令。CMAC就是为了解决这个问题。原理认证成功后芯片和读写器会基于刚才交换的随机数RndA, RndB推导出一个临时的会话密钥Session Key。此后双方用这个会话密钥为每一条指令和响应计算一个“消息认证码”CMAC附在数据后面。接收方用同样的密钥和算法计算CMAC如果匹配说明消息完整且来自合法的通信方有效防止了篡改和重放。启用通过设置配置页中的SEC_MSG_EN位来启用CMAC安全消息模式。一旦启用芯片将只接受附带有效CMAC的命令除AUTHENTICATE本身。核心机制命令计数器CmdCtr一个2字节的计数器认证成功后从0000h开始。每成功处理一条命令及响应后递增。它被纳入每条命令和响应的CMAC计算中是防重放攻击的关键。即使攻击者截获了一条有效的带CMAC的命令因为计数器值已经变化重放该命令会被识别为无效。CMAC计算范围对于命令CMAC计算数据包括CmdCtr || Command Code || Arguments对于响应则是CmdCtr || Response Data。CRC校验字节不参与CMAC计算但发送时仍需附加在CMAC之后。截断TruncationMIFARE Ultralight AES采用一种特殊的“MFP截断法”不是取CMAC值的前8字节而是取所有偶数位置的字节从0开始计数。例如一个16字节的CMACC0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF截断后为C0 C2 C4 C6 C8 CA CC CE。实战示例带CMAC的读操作假设会话密钥已生成命令计数器为0002h。构造输入数据要发送读页04h的命令命令码是30h参数是04h。输入数据为0200 30 04CmdCtr LSB在前然后是命令和参数。计算CMAC使用会话密钥对上述输入数据计算完整的16字节CMAC。截断CMAC使用MFP截断法得到8字节的CMACT。发送命令发送30 04 [8字节CMACT]。验证响应芯片返回数据后会附带8字节的CMACT。你需要用新的命令计数器值此时应为0003h和收到的数据重新计算并截断CMAC与收到的CMACT比对。注意事项CMAC模式下的“静默”响应在普通模式下写入成功通常返回一个0AACK。在CMAC安全消息模式下对于某些预期只返回ACK的命令如成功的WRITE芯片的响应会变为仅返回该命令对应的CMACT而不再有0A状态码。你的读卡器程序必须适应这种变化将接收到的8字节数据直接视为CMACT进行验证而不是将其解析为状态码数据。4. 安全计数器与OTP的防撕毁应用实践对于票务、次卡等应用一个可靠且防篡改的计数器是核心。MIFARE Ultralight AES提供了3个24位单向计数器其中计数器2CNT2可以通过AES认证进行保护。4.1 受保护计数器的使用流程使用受AES保护的计数器2是最佳实践。流程如下启用保护在配置页中将CNT_INC_EN和CNT_RD_EN位设为0。这意味着对计数器2的“读”和“增”操作都需要先通过Key0认证。认证执行前述的AES认证流程使芯片进入AUTHENTICATED状态。读当前值发送READ_CNT命令读取计数器2的当前值。建议在安全消息模式下进行以验证响应完整性。递增发送INCR_CNT命令指定递增的值1-FFFFFFh。值为0是合法的但实际不会增加。处理响应成功芯片返回操作成功的响应在安全消息模式下是CMACT。NAK5或NAK7这通常意味着发生了撕毁事件。在递增操作完成前卡片离开了读写器场区。此时计数器的值可能处于不确定状态可能已增可能未增。标准处理流程是回到步骤3重新读取并尝试递增。NAK6计数器已损坏或不可用。对于票务应用这意味着此票卡应被永久禁用例如通过设置OTP中的某个位或写入特定标志到受保护内存。验证可选但推荐在非安全消息模式下强烈建议在递增操作后再次读取计数器确认值已正确更新。在安全消息模式下如果收到了有效的CMACT响应则可以省去此验证步骤因为CMAC已经保证了命令被完整执行。4.2 将OTP区域用作简易计数器除了专用计数器页03h的32位OTP区域也可被巧妙地用作计数器。所有位出厂为0只能一次性烧写为1。你可以将其视为一个最多32次的递减计数器。推荐实现方法防误操作不是从全0开始写1而是从某个特定值开始。例如如果你需要提供4次服务可以在个人化时将OTP页初始化为0x0F FF FF FF二进制00001111...。每次使用将最高位的某个0烧写成1。第一次使用后变为0x1F FF FF FF第二次0x3F FF FF FF第三次0x7F FF FF FF第四次0xFF FF FF FF。这样通过检查从最高位开始连续1的个数就能知道剩余次数。这种方法比从低位开始写更可靠因为高位比特在物理布局上可能更稳定且逻辑判断简单。关键优势OTP操作本身是防撕毁的。即使在烧写过程中发生撕毁该比特要么保持原值0要么变为目标值1绝不会处于中间状态。这保证了计数器状态的确定性。4.3 防欺诈的计数器初始化策略即使有了AES保护对于计数器1和3如果不使用或者在某些极端假设下仍需考虑防数据篡改。这里提供两个系统级思路内存存储MAC在用户内存中除了存储计数器值再存储一个基于“计数器值 || UID || 其他固定数据”计算出的MAC消息认证码。这个MAC在后台系统用安全密钥生成写入卡片。每次读卡时终端或后台重新计算MAC并与存储的比对不一致则说明数据被篡改。这相当于在卡片层面增加了一层软件校验。后端关联校验在后台数据库中记录每张卡UID的最后一次合法计数器值。当终端读到一张卡时不仅校验卡片内的逻辑如CMAC还将UID 当前计数器值上报后台。后台检查该计数器值是否合理例如是否大于上次记录的值增长幅度是否在合理时间窗口内。发现异常则拉黑该UID。这是一种基于行为分析的防御。5. 芯片真伪鉴别与数字签名管理在对抗克隆卡和假冒芯片的斗争中MIFARE Ultralight AES提供了基于椭圆曲线数字签名ECDSA的原厂鉴别功能。5.1 NXP原厂签名及其验证每颗芯片在出厂时都使用NXP的私钥对其自身的UID计算了一个48字节的ECDSA签名并存储在芯片的签名区域。你可以通过READ_SIG命令读取它。验证流程从芯片读取UID和48字节签名。使用NXP公开的ECC公钥基于secp192r1曲线和标准ECDSA验证算法对“UID”和“签名”进行验证。如果验证通过则证明该芯片是由NXP生产的正品。重要限制这个机制主要用于批量真伪鉴别防止大量非NXP生产的兼容芯片流入你的系统。它不能防止针对单张卡的克隆。因为攻击者可以从一张正品卡上完整地复制UID和签名然后烧录到克隆芯片中。因此原厂签名验证必须与UID重复检测机制结合使用。后台系统应记录每次交易使用的UID如果发现同一个UID在不可能的时间或地点同时出现则很可能遭遇了克隆卡。5.2 自定义签名与锁定芯片允许你用自定义签名覆盖NXP的原厂签名。这通过WRITE_SIG命令实现需要逐页共12页写入48字节的新签名数据。签名区域有三种状态由LOCK_SIG命令控制解锁态可以写入新签名。锁定态不能写入但可以通过LOCK_SIG命令解锁。永久锁定态永远不能写入也无法解锁。应用场景你可以在个人化阶段使用自己系统的一对ECC密钥为每张卡生成一个基于其UID的系统专属签名并覆盖掉原厂签名。这样你的读写器在验卡时就用你自己系统的公钥来验证这个签名。这不仅能验证芯片真伪还能验证这张卡是否是“你系统发行的”合法卡片实现了双重鉴别。操作警告签名锁定流程修改签名是一个高风险操作。务必遵循以下顺序LOCK_SIG 00h确保签名区处于解锁状态。循环执行WRITE_SIG写入12页新签名数据。立即执行LOCK_SIG 02h将签名区永久锁定。 千万不要在写入后停留在锁定态LOCK_SIG 01h。因为锁定态是可逆的如果攻击者获取了你的LOCK_SIG 00h命令他可以解锁签名区并篡改你的系统签名。永久锁定是唯一安全的选择。同样在启用CMAC安全消息后WRITE_SIG和LOCK_SIG命令也必须附带有效的CMAC。