从刷卡到指纹:三个小实验让我重新理解物联网终端的“信任链”
一开始做 RC522 的时候,我对“物联网实验”的理解其实很直接:能刷到卡、屏幕有反应、余额能变化,这个实验大概就算跑通了。
后来连续做了 RFID 消费充值、RFID 卡内数据加解密和 AS608 指纹识别,我才慢慢发现,这几个看起来很小的实验,碰到的其实是同一类问题:
设备到底识别到了什么?识别之后凭什么允许它继续操作?数据改完以后,怎么确认真的改成功了?如果设备以后接入网络,这些本地结果又该怎样被可信地带到服务器?
ITU-T 对物联网的定义里专门提到了 identification、data capture、processing 和 communication,也就是识别、数据采集、处理与通信这些能力。[^1] 回头看我做的三个实验,前面三项已经很明显,只是“真正联网”这一环还比较弱。所以我现在更愿意把它们叫作物联网端侧原型,而不是把一块开发板加一个传感器就直接说成完整物联网系统。
这篇就从一张 RFID 卡说起。
1. “读到卡”其实只是第一步
我用的 RFID 这一侧是 RC522 模块,主控是 STM32L4。MFRC522 本身是一颗工作在 13.56 MHz 的非接触式读写芯片,支持 ISO/IEC 14443 A / MIFARE,并且给主机提供 SPI、UART 和 I²C 等接口。[^2] 在我的工程里,STM32 与 RC522 走 SPI,所以从主控角度看,它不是直接“读卡”,而是在通过 SPI 控制 RC522,再由 RC522 去完成射频侧的通信。
这一层关系刚开始很容易被忽略。
RFID 卡
↕ 13.56 MHz 射频
RC522
↕ SPI
STM32
↕
TFT / 串口 / 蜂鸣器
我第一次把整条流程顺下来以后,才意识到一次“刷卡成功”至少包含了几步:
RC522_Search_Cards(...); // 寻卡
RC522_Anticoll(...); // 防冲突并取得卡片标识
RC522_Select_Cards(...); // 选中本次操作的卡
RC522_Verification_Password(...); // 扇区认证
RC522_Read_Date(...); // 读数据
后面如果要扣费或者充值,还会继续进入数值块的加减值和写回。
这不是为了把流程写得复杂。MIFARE Classic 的存储结构本来就是按扇区和块组织的,扇区最后一个块是 Sector Trailer,里面放 Key A、可选的 Key B 和访问控制位;卡片在执行内存操作之前需要先完成选择和认证。[^3] 所以“天线感应到卡片”和“我有权限修改这个扇区”根本不是一回事。
这也是我从这个实验里得到的第一个比较重要的认识:
物联网里的“检测到一个对象”,不等于“这个对象已经通过授权”。
很多演示程序为了让效果尽快出来,会让这两个概念挨得非常近,看起来就像“刷一下卡 → 余额变化”。但把底层调用展开以后,中间其实隔着识别、选择、认证和数据访问几个阶段。
2. 100 → 90 → 65 → 115,比“屏幕显示成功”更有说服力
消费充值实验里,我把初始金额设成了 100 元,然后依次做了三次操作:
操作 | 计算 | 我实际读回的余额 |
|---|---|---|
初始化 | — | 100 |
扣费 10 元 | 100 - 10 | 90 |
扣费 25 元 | 90 - 25 | 65 |
充值 50 元 | 65 + 50 | 115 |
真正让我觉得这个实验有价值的,不是 TFT 上出现了“扣费成功”几个字,而是卡里的值确实沿着 100 → 90 → 65 → 115 变化,而且操作后还能重新读回来。
MIFARE Classic 的 Value Block 本来就是为类似电子钱包的数值操作设计的,规格里明确给出了 increment、decrement、restore、transfer 等命令;其中值是 4 字节有符号整数,并采用带冗余的固定格式保存。[^3]
我这套程序在执行完加值或减值以后,会再次读取同一个数据块。这个“写后复读”看起来只是多了一次读卡,实际却很有用:
读取旧值
↓
判断当前模式
↓
扣费 / 充值
↓
写入或执行数值操作
↓
重新读取
↓
用真实读回值刷新 TFT 和串口
因为一个函数返回 MI_OK,只能说明它报告这一步执行成功;重新读出目标数据,至少能再确认一次最终状态是否符合预期。
当然,复读也不是万能验证。它不能替代完整的事务机制,更不能单独解决掉电、并发写入或者恶意篡改。但作为嵌入式实验里的最小闭环,它比“调用完函数就打印 success”可靠得多。
从这里我开始把一个终端分成两层来看:
底层关心“卡能不能被正确访问”;
上层关心“这次访问代表扣费、充值还是初始化”。
也就是驱动成功不等于业务成功。
3。默认密钥能让实验跑起来,但不能把它当成安全设计
MIFARE Classic 出厂状态下,扇区密钥默认是 FF FF FF FF FF FF。NXP 的规格明确写到,出厂时所有密钥被设置为 FFFF FFFF FFFFh。[^3]
教学实验里用默认密钥非常方便:省掉了发卡、密钥分配和密钥管理这一大块内容,先把读写链路跑通。
但如果把这套做法直接搬到真实门禁、支付或者设备权限控制里,我现在会非常谨慎。
NXP 自己也在 MIFARE Classic 产品页上提醒:面向安全相关的应用,应考虑 MIFARE DESFire 或 MIFARE Plus 系列。[^4] 所以我不会把“能通过 Key A 认证”写成“系统已经安全”,更不会把默认密钥当成真正的访问控制方案。
这件事其实很像很多物联网产品的早期原型:功能已经能跑,但安全只是为了开发方便而存在。
如果继续往产品方向走,我认为至少要补上这些东西:独立的密钥配置、权限分级、异常状态处理、可追溯的操作记录,以及对调试接口和网络接口的访问限制。NIST 的 IoT 设备安全基线里也把设备识别、数据保护、接口逻辑访问、软件更新和安全状态感知列为核心能力。[^5]
换句话说,“有密码”只是安全的一小部分。
4。第二个实验让我发现:卡片认证和数据加密是两件完全不同的事
后来我又在同一类 RFID 卡上做了 DES 加解密。
这个实验里,我写入的原始字符串是:
wangxiwen
卡的数据块是 16 字节,而 DES 每次处理 64 bit,也就是 8 字节。NIST 的 FIPS 46-3 对 DES 的定义中,密钥输入是 64 bit,其中 56 bit 实际参与算法,其余 8 bit 用于奇偶校验。[^6]
所以我的程序把这 16 字节拆成两组:
第 1 组:前 8 字节
第 2 组:后 8 字节
wangxiwen 一共 9 个 ASCII 字符,放进 16 字节数组以后,后面没有占满的位置补零。然后程序分别调用两次 Process_Message() 完成处理。
这时一个很容易混淆的点就出现了:
扇区 Key A 是为了取得卡片存储区的访问权限,DES 密钥则是为了变换业务数据。
它们解决的是不同问题。
卡片认证回答的是:我现在能不能读写这个扇区?
数据加密回答的是:别人即使看到了这些业务字节,能不能直接理解原文?
这两个层次不能互相替代。
5. “出现乱码”不等于“加密正确”
这次实验里有个现象很直观:初始化后 TFT 和串口能看到 wangxiwen,执行加密以后,数据区域变成了不可直接阅读的字符;再解密以后,又恢复成 wangxiwen。
但我后来不太愿意把“加密后出现乱码”当作证明。
原因很简单:密文是二进制字节序列,里面完全可能出现不可打印字符,甚至 0x00。如果程序硬把它当 C 字符串显示,就可能提前在零字节处截断,也可能显示成各种看起来很乱的符号。
所以:
乱码只能说明“这串字节不适合按普通文本显示”,不能单独证明密码算法工作正确。
我现在觉得更靠谱的验证方式应该是:
原始 16 字节
↓
按十六进制打印
↓
加密
↓
再次按十六进制打印 16 字节
↓
解密
↓
逐字节比较原始数据与恢复数据
例如最后用 memcmp() 比较完整 16 字节,而不是只比较字符串表面上有没有恢复。
我自己的实验已经完成了“明文 → 非明文数据 → 恢复原文”的闭环,这能支持“加解密流程可逆、程序链路能跑通”这个结论;但如果要严格验证每个字节,十六进制读回和逐字节比对会更扎实。
这种区别对我影响挺大。做嵌入式调试时,“现象看起来对”与“证据足够证明它对”其实是两回事。
6. DES 适合用来理解分组密码,但不适合让我产生“已经安全”的错觉
DES 这个实验最适合学的是分组、密钥、加密和解密之间的关系。
但它不应该让我产生“给卡内数据套了 DES 就安全了”的错觉。
FIPS 46-3 已经在 2005 年被 NIST 正式撤销;更早的 FIPS 46-3 版本甚至已经明确把单 DES 限制在遗留系统,并指出随着硬件发展,穷举 DES 密钥越来越可行。[^6] 现在的 AES 标准使用 128 bit 数据块,并定义 AES-128、AES-192 和 AES-256 三种密钥长度。[^7]
所以如果是课程实验,我会保留 DES,因为它足够小,流程也容易观察。
如果是准备部署的物联网设备,我会把重点移到:
现代算法 + 正确的工作模式 + 密钥管理 + 完整性/认证保护。
我这次程序里还用了 0x5A 0xA5 作为“已经加密”的状态标志。这个设计对实验很方便,可以阻止重复加密和无效解密;但它只是一个业务状态标记,并不是密码学意义上的完整性校验。别人如果能改写这个状态字节,单看它本身无法证明密文没有被改动。
这一点也正好对应 NIST IoT 安全基线里的 Data Protection:不仅要考虑数据是否泄露,还要考虑设备数据遭到未授权修改的问题。[^5]
7。到了指纹实验,“身份”从一张卡变成了人的生物特征
RFID 更像“我持有什么”,而 AS608 指纹识别更接近“我是谁”。
我这次用 STM32L431VC 配合 AS608,模块和 MCU 之间通过串口收发,同时还有一个手指检测状态引脚。程序里真正让我看明白的是指纹录入和识别并不是“拍一张图然后比图片”。
录入的大致链路是:
第一次按压
↓
PS_GetImage()
↓
PS_GenChar(CharBuffer1)
↓
第二次按压
↓
PS_GetImage()
↓
PS_GenChar(CharBuffer2)
↓
PS_Match()
↓
PS_RegModel()
↓
PS_StoreChar()
识别时则是:
PS_GetImage()
↓
PS_GenChar(CharBuffer1)
↓
PS_HighSpeedSearch(...)
↓
匹配成功 / 失败
AS608 的公开驱动实现和开发者手册接口中也能看到同样的一组命令:采集图像、生成特征、合并模板、存储模板以及搜索模板;常见 AS608 模块的指纹库容量为 300 枚。[^8]
我实际录入一枚指纹后,串口先出现两次采集和一致性比对相关提示,模板保存成功;查询剩余位置时得到 299。之后用已录入手指测试,LCD 和串口都显示识别成功;换成没有录入的手指,LCD 显示识别失败。
这里我第一次比较直观地理解了“模板”这个词。
在生物识别系统里,后续匹配通常针对的是用于比较的生物特征参考数据,而不是每一次都拿两张原始图片做肉眼比较。NIST 对 biometric reference 的定义也是“一个或多个已存储的生物特征样本、模板或模型,用来与输入样本进行比较”。[^9]
但我不会进一步宣称“模板绝对无法还原原始指纹”之类的话,因为仅靠这次实验并不能证明这一点。能确定的是:我的业务代码真正操作的是 AS608 生成和保存的特征/模板,并通过搜索接口得到匹配结果。
8。一个 get time err,反而让我更理解怎么调物联网程序
指纹识别时我遇到过一个挺有意思的问题。
串口在“指纹识别成功”附近出现过:
get time err
如果只盯着这句,很容易以为“指纹识别失败了”。
但顺着代码往下看,逻辑其实是:
PS_HighSpeedSearch(...); // 指纹搜索已经成功
Finger_Get_Time(); // 再尝试获取时间
while (time_flag == 0) {
if (HAL_GetTick() - wdttimetick >= 2000) {
printf("get time err");
break;
}
}
printf("指纹识别成功:XiWen");
g_ViewAS608State = 1;
也就是说,这个报错属于后面的时间获取支路超时,不是 AS608 的本地指纹搜索失败。
这个小问题对我挺有启发。
物联网设备一旦开始联网,错误来源会突然变多:
传感器/识别模块
↓
驱动
↓
业务逻辑
↓
本地状态
↓
网络
↓
时间服务 / API / 云端
上面任何一层出错,都可能在同一个串口里打印日志。
如果没有把错误归属到具体阶段,一条“网络超时”完全可能被误判成“传感器坏了”。
所以后来我越来越觉得,物联网终端的日志不能只写“成功/失败”。至少要区分模块握手失败、认证失败、数据块读写失败、模板搜索失败、时间同步失败、网络上报失败等状态。
这就是可观测性在小型嵌入式设备里的最初形态。
9. LiteOS 的多任务结构,让我看到“功能叠加”之后为什么需要调度
指纹工程还有一个和前两个 RFID 实验不太一样的地方:它用了 Huawei LiteOS,把控制、显示和指纹处理拆成不同任务。
我当时看到的是三个比较清楚的职责:
control_task → 控制相关逻辑
view_task → LCD、界面与按键
finger_task → AS608 录入、删除、查询和识别
Huawei LiteOS 的任务模型本身就包含 Ready、Running、Blocked 等状态,并由内核根据优先级进行调度和任务切换。[^10]
在一个只做“刷卡 → 打印一句话”的程序里,裸循环已经够用。
但只要功能开始叠加,比如:
一边等待指纹;
一边刷新 LCD;
一边扫描按键;
一边获取时间;
后面还可能再加 Wi-Fi 上报;
如果所有逻辑都塞在同一个大 while(1) 里,阻塞和耦合会越来越明显。
这个时候 RTOS 的价值才从“课程里的一个名词”变成了我能看见的东西:它不是让传感器识别得更准,而是让多个功能在一个设备里更有秩序地共存。
10。把三个实验叠在一起,我看到的其实是一条端侧“信任链”
做完以后,我试着不按“RFID 实验 / DES 实验 / 指纹实验”来分,而是按一个终端真正处理事情的顺序重新整理了一遍。
环节 | 我做过的实现 | 它回答的问题 |
|---|---|---|
物理输入 | RFID 卡、手指、按键 | 外界发生了什么? |
设备通信 | RC522-SPI、AS608-UART | 数据能不能可靠进入 MCU? |
识别 | 卡片选择、指纹模板搜索 | 当前对象是谁/是哪一个? |
访问控制 | MIFARE 扇区认证 | 有没有权限访问目标数据? |
业务处理 | 扣费、充值、初始化 | 识别以后要做什么? |
数据保护 | DES 实验 | 数据落在介质里后怎样减少明文暴露? |
状态验证 | 写后复读、成功/失败对照 | 操作真的生效了吗? |
人机反馈 | TFT、LCD、串口、蜂鸣器 | 人怎么知道设备刚刚做了什么? |
任务调度 | LiteOS 多任务 | 多条业务链怎么同时运行? |
网络扩展 | 时间同步、后续 Wi-Fi 上报 | 本地结果怎样进入更大的系统? |
这一张表对我来说,比单独记住某个寄存器或者某个函数更有意义。
因为换一个传感器、换一种卡、甚至换一个主控,具体 API 会变,但这些问题不会消失。
11。如果继续把它做成真正的物联网终端,我会先补什么
假设现在让我把这三套实验拼成一个“实验室门禁 + 考勤 + 小额消费”终端,我不会第一时间去做一个很大的云后台。
我反而会先把端侧几个基础问题补好。
身份不要只剩一个字符串
AS608 里保存模板 ID,业务层再维护:
模板 ID → 用户 ID → 权限
RFID 也类似,把卡片标识当成索引,而不是把所有业务信息都硬编码在 MCU 里。
这样以后姓名、学号、权限发生变化,不需要重写底层识别逻辑。
默认密钥退出正式环境
实验卡用 FF FF FF FF FF FF 没问题,但部署时不应该继续保留。对于真正强调安全的卡片应用,我也不会默认 MIFARE Classic 足够用,而会根据风险评估更现代的卡片方案。NXP 对安全相关应用给出的产品建议已经很明确。[^4]
加密状态和数据完整性分开设计
5A A5 很适合做状态机,但它不能证明数据没被改。
真正的业务记录还应该有版本、长度、计数器/交易号等必要元数据,并根据威胁模型加入适当的完整性或认证保护。至于具体用什么算法和协议,要看设备算力、卡片能力、网络架构和密钥怎么管理,不能只因为“AES 比 DES 新”就随便套一个函数。
把“重复触发”当成正式问题
RFID 卡停在天线区、手指一直压在传感器上,都可能造成重复处理。
我的 RFID 程序会在操作后把工作模式清零;指纹工程也有按压状态、计数过滤和时间限制来压制重复识别。再往后做,我会给关键业务加入交易序号、时间窗口或明确的状态机,而不是只靠延时。
本地先能工作,联网再同步
这个思路我现在比较认同。
门禁、考勤或者本地消费,如果网络断一下就彻底不可用,体验会很差。端侧可以先完成身份判断和本地记录,把“用户 ID、设备 ID、时间、事件类型、结果”缓存下来,网络恢复后再上传。
这样云端负责集中管理和分析,本地终端负责保证现场业务能继续跑。
12。我现在理解的物联网,不是“传感器 + Wi-Fi”
以前看到“物联网”三个字,我很容易先想到 MQTT、云平台、手机 App、数据大屏。
但做完这几个小实验以后,我反而觉得真正容易被忽略的是网络之前的部分。
一张卡贴到天线上,一枚手指压到采集器上,设备先要回答:
我检测到了什么?
↓
我能不能正确识别它?
↓
它有没有权限?
↓
我应该修改什么状态?
↓
修改真的成功了吗?
↓
这个过程能不能被追溯?
↓
如果要联网,哪些数据值得上传?
这些问题都没有回答清楚之前,接上 Wi-Fi 只是让一个不够可靠的本地系统变成了一个“不够可靠但能联网”的系统。
反过来,如果端侧已经把识别、访问、业务、验证、错误状态和最小必要数据理清楚,再把它接到网络里,整个系统会自然很多。
所以这三个实验给我留下最深的东西,不是 RC522 的某个引脚,也不是 AS608 的某一条命令,而是一个很朴素的判断:
物联网真正有价值的地方,不是“物”能发数据,而是系统知道这份数据从哪里来、为什么可信、允许它触发什么动作,以及动作发生以后还能不能说清楚。
这大概也是我现在对“端侧智能”和“可信物联”最具体的一次理解。
参考链接
下面只列我写这篇时实际核对过的公开来源。实验现象和数值来自我自己的实际运行结果;协议、芯片行为、密码算法与物联网安全相关结论尽量以标准组织或芯片厂商页面为准。AS608 的公开原厂页面较难检索,因此其命令接口额外用公开驱动实现交叉核对,我没有据此延伸无法验证的硬件指标。
[^1]: ITU-T Y.4000 / Y.2060 对 IoT 的定义与识别、采集、处理、通信能力说明: https://www.itu.int/en/publications/Documents/tsb/2016-InternetOfThings/files/basic-html/page21.html
[^2]: NXP, MFRC522 Standard performance MIFARE and NTAG frontend,包含 13.56 MHz、ISO/IEC 14443 A/MIFARE、SPI/UART/I²C 主机接口等说明: https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf
[^3]: NXP, MIFARE Classic EV1 1K,包含扇区结构、Sector Trailer、Key A/Key B、出厂密钥、认证要求、Value Block 以及 Increment/Decrement 等命令: https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf
[^4]: NXP, MIFARE Classic 产品页。NXP 对 security-relevant applications 建议参考 MIFARE DESFire 和 MIFARE Plus 系列: https://www.nxp.com/products/rfid-nfc/mifare-hf/mifare-classic%3AMC_41863
[^5]: NISTIR 8259A, IoT Device Cybersecurity Capability Core Baseline;以及 NIST IoT Technical Capabilities Catalog: https://csrc.nist.gov/pubs/ir/8259/a/finalhttps://pages.nist.gov/IoT-Device-Cybersecurity-Requirement-Catalogs/technical/
[^6]: NIST FIPS 46-3, Data Encryption Standard (DES)。该标准已于 2005-05-19 撤销;正文给出 DES 64-bit key 中 56 bit 参与算法,并讨论了单 DES 的穷举风险与遗留用途: https://csrc.nist.gov/pubs/fips/46-3/finalhttps://csrc.nist.gov/files/pubs/fips/46-3/final/docs/fips46-3.pdf
[^7]: NIST FIPS 197-upd1, Advanced Encryption Standard (AES),AES 使用 128-bit 数据块,定义 AES-128、AES-192、AES-256: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf
[^8]: AS608 公开驱动实现(README 说明其接口按官方开发手册实现),可核对 PS_GetImage、PS_GenChar、PS_RegModel、PS_StoreChar、搜索以及 300 枚容量等常见接口行为: https://github.com/Leopard-C/AS608 STMicroelectronics 中文社区中的 STM32 + AS608 接口实现也可用于交叉核对: https://shequ.stmicroelectronics.cn/thread-637960-1-1.html
[^9]: NIST Computer Security Resource Center, “biometric reference” 术语定义(用于与输入生物特征样本比较的已存储样本、模板或模型): https://csrc.nist.gov/glossary/term/biometric_reference
[^10]: Huawei LiteOS 内核任务说明,包括 Ready / Running / Blocked / Dead 状态、任务优先级、上下文保存与任务切换: https://support.huaweicloud.com/kernelmanual-LiteOS/zh-cn_topic_0145350125.html
写评论
读完想说的,写在这里。
加载评论中…