MagSpoof编码原理深度解析:磁条数据、奇偶校验与LRC校验全解
【免费下载链接】magspoof_flipperPort of Samy Kamkar's MagSpoof project (http://samy.pl/magspoof/) to the Flipper Zero. Enables wireless emulation of magstripe data, primarily over GPIO, with additional experimental internal TX.项目地址: https://gitcode.com/gh_mirrors/ma/magspoof_flipper
MagSpoof 是最经典的磁条模拟(Magstripe Emulation)开源项目,它把磁条卡上存储的二进制数据转化为磁场脉冲,让读卡器误以为真的有一张卡被刷过。本文从MagSpoof编码原理出发,用通俗的语言拆解磁条数据的 ISO 7811 三磁道结构、字符奇偶校验与 LRC 纵向冗余校验,并对照 Flipper Zero 移植版 magspoof_flipper 的实际源码逐行验证。读完本文,你不仅能看懂.mag文件里每串字符的真实含义,还能理解为什么"校验位"是磁条模拟成功与否的关键。
什么是MagSpoof编码原理?从物理磁条到无线模拟
磁条卡的"本体"是一小段磁带,上面按 ISO 7811 标准划分出 3 条磁道(Track)。刷卡时,读卡器的磁头扫过磁条,因电磁感应产生微弱的电压脉冲,脉冲的"有无"与"间隔"就是数据本身。MagSpoof 逆向复现了这个过程:它不做物理磁条,而是用一个线圈主动产生变化的磁场,把磁道数据"隔空广播"给读卡器——这就是MagSpoof编码原理的核心思想。
在 magspoof_flipper 项目中,编码主流程全部集中在helpers/mag_helpers.c这一个文件里:mag_encode()负责把字符串变成带校验的比特流,play_track()负责把比特流按时序逐位发射,mag_spoof()则统筹整次模拟过程。三个函数加起来不过两百行,却完整覆盖了"字符 → 校验 → 物理层编码 → 发射"的全链路,是理解磁条编码最好的活教材。
磁条数据格式入门:ISO 7811标准与三条磁道
在看校验算法之前,必须先理解磁条数据是怎么"排版"的。三条磁道的容量、字符编码和格式各不相同,但共享同一套校验体系。源码helpers/mag_helpers.c中的两个数组已经剧透了一切:
bitlen[] = {7, 5, 5}:三条磁道每个字符占用的总位数(含校验位);sublen[] = {32, 48, 48}:三条磁道的 ASCII 偏移量。
三条磁道的核心参数对比如下:
| 磁道 | 每字符位数 | 其中数据位 | 校验位 | 字符集 | ASCII 偏移 | 典型格式 |
|---|---|---|---|---|---|---|
| Track 1 | 7 | 6 | 1 | 字母+数字 | 32(空格=0) | %B卡号^姓名^有效期? |
| Track 2 | 5 | 4 | 1 | 纯数字(BCD) | 48('0'=0) | ;卡号=有效期? |
| Track 3 | 5 | 4 | 1 | 纯数字(BCD) | 48('0'=0) | ;...? |
磁道1:7位字母数字编码
磁道1 支持字母数字,密度 210 bpi,容量约 79 个字符,用于存卡号、持卡人姓名、有效期等。它的每个字符占 7 位 = 6 位数据 + 1 位校验。由于是 ASCII 系,存储时会把字符的 ASCII 码减去偏移 32,让空格正好变成 0x00。参考项目自带的示例文件resources/SamyExampleImage.mag:
Track 1: %B426684131234567^LASTNAME/FIRST^YYMMSSSDDDDDDDDDDDDDDDDDDDDDDDDD?%是起始符,^是分隔符(分隔卡号、姓名、有效期),?是结束符。
磁道2与磁道3:5位BCD数字编码
磁道2(密度 75 bpi)和磁道3 只支持数字,每个字符占 5 位 = 4 位 BCD 数据 + 1 位校验。注意它的偏移是 48,即字符'0'的 ASCII 码 48 减去 48 后变成 0x00,刚好对应 BCD 的 0000。同样来自示例文件:
Track 2: ;426684131234567=230188855555555555555?;是起始符,=是分隔符,?是结束符。磁道2 是最常用的一条,绝大多数 POS 机刷卡主要就读它。
起始符、分隔符与结束符:数据如何"排版"
磁道数据必须用固定哨兵字符包裹:磁道1 用%开头、?结尾,磁道2/3 用;开头、?结尾。读卡器只有看到起始符才会开始解析数据,看到结束符才停止。如果你在 Flipper 上通过 UART 读卡,解析工作由mag_device.c中的mag_device_parse_card_string()完成,它会按%...?、;...?、;...?的顺序切出三条磁道——这也解释了为什么磁道2 示例里既有;又有=却不会串数据。
奇偶校验详解:让读卡器识别每个字符
磁条数据在传输时会受到噪声、刷卡速度不均匀等因素干扰,所以每个字符都带一位**奇校验(Odd Parity)**位。规则很简单:字符的数据位加上校验位,总共有 1 的个数必须是奇数。这样读卡器就能在每一位上做自检:如果 1 的个数是偶数,就认为这个字符读错了。
源码里mag_encode()的实现非常巧妙(helpers/mag_helpers.c):
crc = 1; tmp = *(data + i) - track_ascii_offset; for(int j = 0; j < track_bits - 1; j++) { crc ^= tmp & 1; // 逐位异或 ... tmp >>= 1; } // 输出 crc 作为校验位crc从 1 开始,与每一位数据依次异或,最终结果就是奇校验位。举个例子,磁道2 上的字符'4'(BCD 为 0100,按低位在前逐位输出 0、0、1、0):
- 校验位 = 1 ⊕ 0 ⊕ 0 ⊕ 1 ⊕ 0 = 0;
- 发送的 5 位序列为
0 0 1 0 0,其中 1 的个数 = 1(奇数 ✓)。
再看磁道1 的字符'B'(ASCII 0x42 减去偏移 32 得 0x22 = 100010,低位在前 0、1、0、0、0、1):
- 校验位 = 1 ⊕ 0 ⊕ 1 ⊕ 0 ⊕ 0 ⊕ 0 ⊕ 1 = 1;
- 7 位序列中 1 的个数 = 3(奇数 ✓)。
由此可见,每个字符都是"数据位 + 1 位奇校验"的紧凑组合,这也是磁道1 每字符 7 位、磁道2/3 每字符 5 位的由来。
LRC校验全解:纵向冗余校验如何保护整条磁道
奇偶校验只能发现单个字符内的奇数个错误,如果某一位在整条磁道上连续出错,奇偶校验就可能失效。为此,ISO 7811 规定每条磁道的末尾还要追加一个LRC(Longitudinal Redundancy Check,纵向冗余校验)字符。
LRC 的计算方法是按列异或:把磁道上所有字符的同一数据位(第 j 位)全部异或起来,得到 LRC 的第 j 位。也就是说:
- LRC 的第 0 位 = 所有字符第 0 位的异或;
- LRC 的第 1 位 = 所有字符第 1 位的异或;
- ……依此类推。
源码中的累加代码正是如此(helpers/mag_helpers.c的mag_encode()):
lrc ^= (tmp & 1) << j; // 把第 j 位数据累加进 LRC 的第 j 位当所有字符都处理完后,LRC 字符本身也会像普通字符一样被加上自己的奇校验位并发送出去。举个例子,若磁道上只有两个数字'9'和'3'(BCD 分别为 1001 和 0011):
- LRC = 1001 ⊕ 0011 = 1010;
- 该 LRC 值再补 1 位奇校验位后作为最后一个字符发送。
读卡器收到整条磁道后,会把所有字符(含 LRC)按列重新异或一遍,如果结果不是全 0,就说明数据在传输过程中被破坏了。这一"最后防线"保证了即使个别位出错,读卡器也能及时发现并拒绝这笔交易,而不是把错误数据当真的用。
F2F双频编码:磁条数据的物理层表示
校验位解决了"数据对不对"的问题,但磁条上存储的终究是物理磁场,还需要一种编码把 0/1 变成磁通翻转序列。这里用到的正是磁卡专用的F2F(Frequency Double,双频编码),也就是代码注释里所说的"Manchester"编码。
F2F 的规则是:每个位单元(bit cell)的边界必然发生一次磁通翻转(时钟信号),如果数据位是 1,则在位单元中间额外翻转一次。于是:
- 数据 0 → 两次翻转之间间隔一个完整的位单元("长间隔");
- 数据 1 → 中间多一次翻转,间隔减半("短间隔")。
读卡器通过恢复时钟来识别每一位,这就是为什么"刷卡速度"其实不那么重要——只要时钟同步建立起来,数据就能被准确解码。
在helpers/mag_helpers.c中,add_bit_manchester()负责把 1 个原始数据位展开为 2 个"半位(half-bit)",而play_track()则按顺序把每个半位输出到 GPIO 引脚,每个半位持续us_clock微秒(默认 240µs,定义见helpers/mag_types.h)。也就是说:
- 1 个原始数据位 = 2 个半位 ≈ 480µs,对应约 2083 bit/s 的数据速率;
- 数据 1 会在位单元中间产生一次翻转,数据 0 则保持电平不变。
整套发射流程在mag_spoof()中编排:先输出 25 个时钟同步位(ZERO_PREFIX)让读卡器建立时钟,接着播放磁道1、磁道2(中间用 53 个同步位ZERO_BETWEEN隔开),最后补 25 个尾部同步位(ZERO_SUFFIX)。默认情况下整条数据会循环发送 3 次(MAG_STATE_DEFAULT_N_REPEATS),提高刷卡成功率。
编码实战:跟随mag_encode函数生成完整比特流
理解了全部原理后,再看mag_encode()的完整流程就一目了然了(helpers/mag_helpers.c第 404–456 行):
- 逐字符处理:把字符 ASCII 码减去磁道偏移(32 或 48),得到真正的数据值;
- 逐位输出:从最低位开始,把每个数据位写入原始比特流,同时累加进 LRC;
- 计算并输出校验位:奇校验位追加到该字符末尾;
- 重复 1–3直到字符串结束;
- 追加 LRC 字符:把累加好的 LRC 值连同它自己的校验位一起输出;
- 曼彻斯特化:每个原始位通过
add_bit_manchester()展开成 2 个半位,交给play_track()发射。
这个过程对三条磁道完全复用,只是传入的bitlen(7 或 5)和sublen(32 或 48)不同。如果你想亲眼验证,可以在调试模式下运行应用,debug_mag_string()会把原始字节流、逐位序列和曼彻斯特编码后的字节流全部打印出来,和本文的推导一一对照。
从编码到刷卡成功:硬件与实用技巧
编码算对了,最后一步是把比特流变成真实的磁场。magspoof_flipper 默认使用 GPIO 外接模块发射:helpers/mag_helpers.c中的play_halfbit()会根据配置把电平输出到 A7(输入)、A6(输出)、A4(使能)三个引脚,驱动由 H 桥芯片(如 L293D)和线圈组成的电磁铁,见项目自带的接线图:
磁场是否够强、方向是否对准,直接决定了模拟成败。几个实战经验值得收藏:
- 摆放位置:把线圈的边角对准读卡器的读头,往往比正对中央更容易成功;
- 角度与深度:多尝试不同角度和按压深度,找到电磁屏蔽最薄弱的"甜点区";
- 速度控制:虽然 F2F 编码对速度不敏感,但读卡器需要先同步时钟,保持匀速刷卡更稳定;
- 内部发射仅作实验:项目还实验性地支持 RFID / 压电蜂鸣器 / NFC / CC1101 内部发射,但磁场太弱,大部分读卡器难以完整解码,官方将其隐藏在调试模式中。
常见问题:为什么模拟成功率不稳定?
Q:数据明明是对的,为什么读卡器没有反应?A:大概率是硬件问题而非编码问题。先检查接线是否按接线图接好、线圈是否贴近读头;再尝试调整角度、深度和位置。也可以把us_clock从默认 240µs 微调,改变数据速率以匹配读卡器。
Q:奇偶校验错了会怎样?A:读卡器会认为该字符损坏并拒绝整条数据。所以手动编辑.mag文件时,只需保证磁道格式正确(%...?、;...?),校验位由mag_encode()自动生成,一般不需要手工计算。
Q:空磁道可以吗?A:可以。.mag文件允许某些磁道留空,mag_device.c中的mag_device_autoselect_track_state()会自动检测可用磁道并选择最合理的发射组合。
Q:为什么 Flipper 内部发射很难成功?A:读卡器的读头紧贴磁条,磁场必须非常强。内部天线隔着设备外壳,磁场衰减严重,只能作为实验功能尝鲜,稳定使用请务必外接线圈模块。
磁条编码看似古老,但其中"字符校验 + 纵向校验 + 物理层编码"的分层思想至今仍是很多通信协议的原型。希望这篇 MagSpoof编码原理解析能帮你彻底看懂磁条数据的来龙去脉——下次再看.mag文件里那串%B...^...?,你就能在心里直接"翻译"出它的每一位了。
【免费下载链接】magspoof_flipperPort of Samy Kamkar's MagSpoof project (http://samy.pl/magspoof/) to the Flipper Zero. Enables wireless emulation of magstripe data, primarily over GPIO, with additional experimental internal TX.项目地址: https://gitcode.com/gh_mirrors/ma/magspoof_flipper
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考