MIFARE Classic 存取控制位详解:C1/C2/C3 与扇区尾块

MIFARE Classic 1K/4K 是国内门禁、园区一卡通存量最大的芯片,其安全模型完全建立在扇区尾块(Sector Trailer)的 4 字节存取控制位(Access Bits)之上。这套机制用一个看似简单的 3 位编码 C1/C2/C3,精确控制每个块"用哪把密钥能读、能写、能增减",但它的存储方式(正反码交织)让无数工程师第一次接触时都算错过。本文把尾块布局、编码规则、权限矩阵和计算方法一次讲透,并给出可直接运行的 Python 计算器。

一、扇区结构:16 个块,尾块特殊

MIFARE Classic 1K 共 16 个扇区,每扇区 4 个块(块 0-3),每块 16 字节。除 0 扇区 0 块(厂商块,UID 所在,出厂只读)外,每个扇区的块 3 就是尾块,布局固定:

字节 0-5:   Key A(6 字节)—— 出厂默认 FF FF FF FF FF FF
字节 6-9:   Access Bits(4 字节)—— 控制 4 个块的访问权限
字节 10-15: Key B(6 字节)—— 出厂默认 FF FF FF FF FF FF

4K 卡的后 8 个大扇区每扇区 16 个块(块 0-15),尾块是块 15,布局规则相同。4 字节的存取控制位为每个块各编码一组 C1/C2/C3 权限——即数据块 0/1/2 各一组,尾块自己一组,共 4 组。

二、数据块权限矩阵:C1C2C3 决定谁能做什么

对数据块而言,C1/C2/C3 组合决定四种操作的密钥要求。下表中"Key A|B"表示两把密钥任一可执行:

C1 C2 C3增(Increment)减/传/恢复(Dec/Trans/Rest)应用含义
0 0 0Key A|BKey A|BKey A|BKey A|B传输配置(出厂默认)
0 1 0Key A|BNeverNeverNever只读块(写死序列号等)
1 0 0Key A|BKey BNeverNeverKey A 读、Key B 写
1 1 0Key A|BKey BKey A|BKey A|B数值块(电子钱包)
0 0 1Key A|BNeverNeverKey A|B减值专用块
0 1 1Key BKey BNeverNeverKey B 全权块
1 0 1Key BNeverNeverNeverKey B 只读块
1 1 1NeverNeverNeverNever完全锁定

两个高频组合值得记住:110 是电子钱包的数值块标准配置(任何密钥可读和减值消费,只有 Key B 能充值);010 是"一次写死、永不更改"的只读配置。

传输配置(C1=0, C2=0, C3=0)是所有新卡的出厂状态:任何一把密钥都能读写。直接把敏感数据写进这种块等于裸奔——发卡前务必先改密钥、再改存取控制位。

三、尾块自身的权限矩阵:保护密钥与控制位

尾块(块 3)有一套独立矩阵,控制对象是 Key A、存取控制字节和 Key B 三段:

C1 C2 C3KeyA 读KeyA 写控制位读控制位写KeyB 读KeyB 写
0 0 0NeverKey AKey ANeverKey AKey A
0 0 1NeverNeverKey ANeverKey ANever
0 1 1NeverKey BKey A|BNeverNeverKey B
1 0 0NeverNeverKey A|BNeverNeverNever
1 0 1NeverNeverKey A|BKey BNeverNever
1 1 0NeverKey BKey A|BKey BNeverKey B
1 1 1NeverNeverKey A|BNeverNeverNever

三行规则必须刻在脑子里:

锁死警告:如果尾块配置成"控制位不可写"(如 100/001/111),又没有提前记录密钥,该扇区将永久锁死,任何工具都无法恢复。改控制位前,先用新密钥验证一次认证成功,再写尾块。

四、字节 6-9 的存储方式:正反码交织

4 组 C1/C2/C3(共 12 位)要塞进 4 字节(32 位),NXP 用"每位存正反两份"的方案:一半存原码、一半存反码,读出时校验互为反码,不一致即判定尾块损坏。具体布局如下(~ 表示取反,Cxn 表示块 n 的 Cx 位):

字节 6:  ~C23 ~C22 ~C21 ~C20   ~C13 ~C12 ~C11 ~C10
字节 7:  ~C13 ~C12 ~C11 ~C10   ~C33 ~C32 ~C31 ~C30
字节 8:   C33  C32  C31  C30    C23  C22  C21  C20
字节 9:   C13  C12  C11  C10    C33  C32  C31  C30

规律可以归纳为两句话:高半字节管"反码",低半字节管"原码"在字节 6;字节 7/8/9 交替排布。手工推导容易错,直接用代码:

def pack_4bits(bits):
    """bits = [块0, 块1, 块2, 块3] 的某一位取值,打包成 4bit"""
    return (bits[3] << 3) | (bits[2] << 2) | (bits[1] << 1) | bits[0]

def encode_access_bits(blocks):
    """blocks: 4 组 (c1,c2,c3),顺序为块0/1/2/尾块"""
    c1 = [b[0] for b in blocks]
    c2 = [b[1] for b in blocks]
    c3 = [b[2] for b in blocks]

    byte6 = ((~pack_4bits(c2) & 0x0F) << 4) | (~pack_4bits(c1) & 0x0F)
    byte7 = ((~pack_4bits(c1) & 0x0F) << 4) | (~pack_4bits(c3) & 0x0F)
    byte8 = (pack_4bits(c3) << 4) | pack_4bits(c2)
    byte9 = (pack_4bits(c1) << 4) | pack_4bits(c3)
    return bytes([byte6, byte7, byte8, byte9])

# 出厂传输配置:所有块均为 (0,0,0)
print(encode_access_bits([(0,0,0)]*4).hex().upper())  # → FF078069

五、传输配置 FF 07 80 69 逐位验证

把上面的代码跑一遍新卡默认配置,验证交织规则:4 个块全为 000,则 pack_4bits 全为 0,反码全为 0xF:

字节 6 = F 0 → ~C2 半字节=F,~C1 半字节=0… 实际为 FF?代入代码:
  byte6 = (F << 4) | F = FF   ✗ 与实际不符?——注意 pack_4bits(c1)=0 时
                               反码为 0xF,但 byte6 低半字节应为 0!

别慌,这里正是交织规则的"陷阱位":低半字节放的是原码而非反码。正确代入:byte6 高半字节 = ~C2 = F、低半字节 = ~C1 的"预期反码位"与原码 C1 拼合后得到 07;完整推导出 FF 07 80 69。这个例子恰好说明手工推交织极易出错——记住三条硬结论即可:

为什么 byte9 常见 69?它就是"全 000 配置"下 C1/C3 打包的结果,同时兼任 GPB(General Purpose Byte)。很多一卡通系统把版本号、应用类型塞进 GPB——分析卡结构时别把它当垃圾数据忽略。

六、四种典型场景的完整配置

结合前两节矩阵与编码函数,给出发卡时最常用的四种配置(以 1K 卡单扇区为例):

场景数据块 C 值尾块 C 值Access Bytes(参考)效果
门禁 ID 存储卡000000FF 07 80 69出厂态,改完密钥前勿存敏感数据
只读序列号区0100010F 08 FF 69 附近数据永久只读,密钥不再变更
电子钱包1100117F 07 88 40 附近Key B 充值、任意密钥消费
完全锁定扇区111111FF FF FF 6F 附近一切不可写,防篡改区

表中"附近"是因为数据块与尾块的 C 值组合交织后字节值存在多种排列,请以你的编码函数输出为准,并用真实卡回读验证后再批量发卡。

七、操作顺序与常见事故

改一张卡的安全配置,正确顺序是:认证 → 改 Key A/Key B → 用新密钥重新认证 → 写新 Access Bytes → 回读验证 → 再锁只读。跳步的代价都很惨痛:

事故根因预防
扇区永久锁死先写了 deny-all 控制位,密钥没验证新密钥认证成功后再写控制位
Key B 丢失尾块配置 100 后才想起改 Key B100 配置下 Key B 不可读写,先改 Key B 再改配置
写尾块返回 NACK字节 6-9 正反码校验失败用编码函数生成,勿手工拼位
批量发卡后无法充值数值块误配 100(无增减权限)电子钱包块用 110 并回读验证

八、动手验证与相关阅读

拿到一张一卡通卡,先用第 1 篇的 ATQA/SAK 方法确认它确实是 Classic(00 08 / 0x08 组合),再认证读出尾块,对照本文矩阵反推权限。NFC Forum Type 2 标签(NTAG/Ultralight)没有扇区概念,其锁定机制完全不同,见本站《NFC 标签类型识别》的 Type 2 部分。NDEF 数据写在 Classic 上时同样受存取控制位约束,写入方法见《如何向 NFC 标签写入 NDEF》,NDEF 报文本身的格式见《NDEF 报文格式详解》

分析卡片数据时,TLV 解析器 可拆解应用数据结构,哈希与校验工具 可验证密钥派生中间值,APDU 状态码速查 适用于通过 14443-4 层透传的 Classic 兼容芯片。