蓝牙钥匙这几年普及得很快,从汽车无钥匙进入、智能门锁,到电动车、共享单车,几乎成了智能硬件的标配。大家用惯了“走近自动解锁、走远自动上锁”的体验,却很少去想一个事:钥匙和锁之间那几十米的无线信号,到底安不安全?我自己做了一套基于 BLE 的蓝牙钥匙系统,从硬件选型到协议调优都是自己一点点啃下来的,中间最让人寝食难安的,就是“中继攻击”。这个攻击不破解算法、不偷密钥,也不做暴力破解,纯粹靠“延长距离”就能把别人合法的钥匙信号搬到你的门前,几秒钟打开锁,走的时候现场还没有任何破坏痕迹。这篇文章我准备把中继攻击的原理、防护技术拆开揉碎讲一遍,再把我自己在实际项目里用过的防护方案和踩坑记录分享出来。不管你是做嵌入式安全的、做智能锁产品方案的,还是纯粹好奇自己家门锁安不安全的,这篇内容应该都能给你一些参考。
先说清楚中继攻击是个什么玩意儿。用一句话概括:攻击者不需要知道你钥匙里的密钥,也不需要破解任何加密算法,只需要在真实钥匙和锁之间搭一座“桥”,把钥匙的信号实时转发给锁,锁就会以为钥匙就在身边,然后开锁。因为信号是实时转发的,钥匙端和锁端的所有认证流程、加密握手、动态码挑战都能正常完成,从锁的角度看,这就是一把货真价实、距离足够近的合法钥匙。
1. 中继攻击原理拆解:为什么你的钥匙会被“隔空搬运”
1.1 BLE 蓝牙钥匙的工作流程
要理解中继攻击,得先明白蓝牙钥匙的正常通信流程。以我最常用的 BLE 方案为例,整个解锁过程大致是这样的:
- 智能门锁或车载蓝牙模块处于广播状态,周期性地向外发送广播包。
- 蓝牙钥匙(手机 App 或者实体蓝牙钥匙)进入信号覆盖范围,扫描到广播包后发起连接请求。
- 连接建立后,锁端生成一个随机挑战值(Challenge),下发给钥匙端。
- 钥匙端用预共享的密钥对 Challenge 做签名或加密计算,把响应(Response)回传给锁端。
- 锁端验证 Response 的合法性,再结合当前信号强度(RSSI)判断钥匙是否足够近,满足条件后执行开锁操作。
这套流程本身的加密和认证逻辑并没有大问题,问题出在“距离判断”这个环节。锁端判断距离靠的是 RSSI 值,而 RSSI 是一个可以被电磁环境、遮挡物、设备天线方向影响的物理量,更关键的是,这个数值只反映了“信号衰减到多少”,根本证明不了“信号源真的离我有多远”。中继攻击瞄上的就是这一步。
1.2 攻击链:两个人两台设备,就能“搬运”信号
中继攻击的基本模型是攻击者需要两个角色分工协作。一个角色叫“嗅探中继”,拿着便携设备靠近真实钥匙,比如站在车主旁边,或者把设备藏在车主经常出没的电梯口、车库角落。另一个角色叫“发射中继”,拿着另一台设备贴近目标锁具。
具体过程是这样的:
- 嗅探中继靠近车主的合法钥匙,建立信号连接或者监听钥匙发出的信号。
- 嗅探中继把接收到的原始信号通过无线网络(可能是 4G、Wi-Fi 或者另一条射频链路)实时传输给发射中继。
- 发射中继在锁具旁边把信号原样广播或注入进去。
对锁来说,它收到的信号就是合法的钥匙信号,而且因为发射中继就贴在锁旁边,RSSI 值显示信号极强,距离判断直接通过。整个攻击过程是“实时”的,所以锁端如果额外加了一层“动态验证码”或“时间戳验证”,也会被当作正常交互自然通过,因为这些验证信息本身就是由真实钥匙实时生成并转发过来的。
1.3 为什么传统校验方案拦不住中继攻击
很多产品经理或者刚入行的开发会下意识问一句:我们把加密算法做得足够强、把密钥长度拉长,不就能防住中继攻击了吗?答案是——防不住。因为中继攻击不触碰底层密码学,它只是“偷听并转播”,就像一个人站在墙外听到墙内的人说了句暗号,然后对着对讲机把暗号一字不差地传到另一头,开门的人听到暗号是对的,但实际说话的人远在 500 米外。
这也解释了为什么中继攻击会让安全圈这么头疼:它不是在挑战你的算法,而是在挑战你的“信任模型”——默认“信号源等于钥匙”、“距离近等于可信”。算法再强,也弥补不了信任模型里的距离盲区。
1.4 “第48次”意味着什么:反复被验证的现实风险
我在标题里写“第48次”,不是说我自己被攻击了48次,而是这个攻击场景在各类公开测试、学术研究、黑产演练中屡屡得手。很多知名安全团队用一套不到两千元的设备组合,就能在短时间内打开多个主流品牌的蓝牙智能锁和汽车无钥匙进入系统。48次,只是我这些年断断续续整理相关案例时,记录下的一个触目惊心的统计数字。这类攻击的可怕之处在于:成本低、隐蔽性高、成功率极高,且受害者几乎没有察觉。
2. 防护技术全景解析:从算法到行为建模的多层防线
2.1 基于距离证明:核心与难点是“时延”而非“强度”
最直接的防护思路是抛弃“信号强度定距离”的老办法,改用“信号飞行时间定距离”。这个思路在学术界被称为 Distance Bounding Protocol(距离边界协议),核心逻辑是:光速是一个有限常数,信号飞行需要时间。如果一把钥匙声称自己在 1 米内,那信号从钥匙到锁的飞行时间应该只有约 3 纳秒。如果实际测得的单向或往返时延明显超出理论值,那就有理由怀疑信号是经过中继绕了远路。
真实系统中几乎不可能做到纳秒级时间同步,所以工程上普遍使用往返时间(Round-Trip Time, RTT)来间接推算距离。锁端发送一个魔数给钥匙,钥匙收到后原样返回或者做极轻量处理后返回,锁端记录从发出到收到的时间差。这个时间差包含了信号在空中飞行的两段路程,也包含了钥匙端硬件的处理时间。关键在于处理时间必须被严格约束——如果钥匙端能快速响应,这个方案在理论上可以限制中继距离。
但难点也很明显:BLE 协议栈本身的中断调度延迟、蓝牙芯片的随机延迟、手机端 App 的响应时间都可能引入不确定性。所以“基于时延”的方案在专用硬件(比如定制蓝牙钥匙硬件)上表现尚可,在手机 App 场景下容易产生较大的测量抖动。实际工程里,我通常不会单独依赖单一测距方案,而是把时延测距和 RSSI 阈值、行为特征三者联合起来做综合判断。
2.2 信号指纹识别:利用硬件差异识别“冒名设备”
每个蓝牙芯片在生产时都会因为硬件工艺偏差,导致射频信号在频偏、相位噪声、天线效率等方面存在细微差异。这些差异构成了设备唯一的“射频指纹”。正常情况下,同一把合法钥匙发出的信号指纹应该是稳定的。中继攻击虽然能完整转播数据内容,但无法复制发射机的模拟前端特性——或者说,要复制相当困难,因为模拟层面的随机偏差不是数字信号能够轻易模拟的。
在防护实践里,我实现了一套简化的信号特征提取机制:
- 锁具在钥匙首次配对时采集并保存钥匙设备的 I/Q 数据偏移、信道频偏、信号包络特征。
- 每次解锁时,锁端重新提取这些模拟特征,与保存的指纹做相似度匹配。
- 如果匹配度低于阈值,即使所有密码学的认证都通过了,也照样拒绝开锁,并触发告警。
这个方案有一个致命缺点:射频指纹会随着温度、湿度、电池电量下降等因素产生漂移。冬天冷风一吹,钥匙的频偏可能就和夏天匹配不上了。我在实际测试中吃过不少苦头,后来采用的折中方案是“允许一定偏差范围 + 设置动态学习窗口”,系统在连续多次合法解锁后自动微调指纹基线,避免因为环境变化造成误杀。
2.3 密钥协商更新与一次性会话:阻止“重放”衍生攻击
中继攻击虽然本身不是重放攻击,但很多攻击者会在中继的基础上做一些变种。比如他们虽然无法破解密钥,但可以录下一段合法的完整解开流程,等车主离开后,再把这段录制数据回放给锁。如果协议设计得不够健壮,锁会以为车主回来了,然后开门。
防御重放攻击的通用做法是“动态挑战 + 单调递增计数器 + 密钥定期更新”。Lock 生成一个随机数,这个随机数只在当前会话中有效,用过后立即作废。与此同时,锁和钥匙两端共同维护一个会话计数器,收到的计数值必须严格大于上一次记录的计数值,从协议层面保证“历史流量无法再次使用”。
我在协议栈里额外做了一层“双向密钥漂移”——每次成功解锁后,两端共享的密钥会基于本次会话的随机数做一次不可逆向的派生,生成一把新的会话密钥。这意味着即使某次会话数据被完整录制,下一次解锁时密钥已经变化,录制数据变成一堆废码。
2.4 行为决策层:锁也可以有“怀疑”机制
密码与信号之外,还可以加一层“行为逻辑”来对抗中继攻击。我给它起的名字叫“锁端怀疑引擎”。它的核心思想很简单:锁不仅要验证“钥匙对不对”,还要验证“这次使用过程像不像正常车主在开门”。举个例子,一个正常车主从靠近门锁到解锁,通常会有 1 到 3 秒的持续信号存在,信号强度会从弱变强或相对稳定。而中继攻击经常出现的情况是:信号在瞬间出现,强度极高且异常稳定,然后就消失了。这不符合正常行为曲线。
我设计的行为特征维度包括:
- 信号出现到解锁请求发出的时间间隔,应该大于某个最小值(通常 800ms 以上)并且小于某个最大值(比如 10 秒)。
- 解锁请求前 2 秒内的 RSSI 方差不能太小,也不能太大。
- 单日解锁次数、解锁时段、连续失败后的行为模式,都会被纳入评分机制。
- 如果触发疑点,锁进入升级认证模式:要求用户输入 PIN 码、使用手机 App 二次确认,或者短时间禁止解锁。
这套机制挡住了不少“自动化、脚本化”的攻击尝试,攻击者要模拟正常行为曲线,就必须增加等待时间、动态调整发射功率,攻击耗时和难度都会显著上升。
2.5 NFC 中继攻击的对照:同样是“信号搬运”,为什么更难防
最近 NFC 中继攻击也成了一个热门话题,很多读者会拿它和蓝牙中继做对比。NFC 的工作距离本来就只有几厘米,中继攻击需要在贴近刷卡设备的“发射中继”和贴近卡片的“嗅探中继”之间建立一条实时传输链路。传统 NFC 刷卡我们默认“贴得足够近”就是可信的,但这个假设同样会被中继攻击打破,而且不少已有门禁系统和支付终端对 NFC 的传输时延要求比较高,一旦实时链路存在几毫秒延迟,就可能导致读卡超时。
相比 BLE,NFC 中继有个明显特点:信道带宽低、交互步数多,对实时性更敏感。这意味着防护方面,除了类似的时延检测,还可以通过“交互时序异常检测”来识别中继路径中引入的额外延迟。如果你是用手机替代实体 NFC 卡的方案,还可以在 App 里叠加生物识别或 LBS 位置围栏,进一步增加攻击难度。
3. 防护方案实践:我的一套可落地的蓝牙钥匙防中继实现
3.1 整体架构选择
我在做系统架构时,把防护方案分成了“不可信链路层”和“可信决策层”两个部分。链路层的任务是尽量压缩中继攻击可操作的空间,主要包括测距、指纹、协议抗重放。决策层的任务是在链路层数据基础上,通过综合评分决定是否开锁。这套分层方式和传统互联网安全里的“纵深防御”是同一个思路,单点失效并不会导致整个系统崩溃。
3.2 Session 结构定义
整个系统里最关键的一个数据结构就是这个会话结构体。它承载了链路层采集到的所有原始数据,也是决策层的输入。我贴一段实际的示例代码,你可以参考这个字段定义去设计自己的会话管理:
struct UnlockSession { // 协议层认证信息 challenge: [u8; 16], response: [u8; 32], counter: u32, // 时延测距数据 rtt_measurements: Vec<u32>, // 单位为微秒 average_rtt_us: u32, // 射频信号特征 rssi_measurements: Vec<i16>, // dBm rssi_variance: f32, rf_fingerprint: RfFingerprint, // 包含频偏、I/Q偏移等 // 行为特征 time_since_first_contact: u64, // 毫秒 unlock_attempt_count: u32, hour_of_day: u8, // 综合评分结果 security_score: u8, // 0-100 decision: Decision, // Allow / Deny / Challenge }每个字段都不是白设计的。比如rtt_measurements我会存最近 5 次测距结果而不是只存一个平均值,因为要看抖动情况——中继链路往往比真实链路抖动更大。rssi_variance用于判断信号变化曲线是否正常,正常开门时 RSSI 会有一个渐进变化过程,而贴脸中转的信号往往是一条平线。
3.3 核心测距模块:从“硬件限制”到“实测经验”
BLE 的往返测距有两种主流方式:一种是把时间戳写在数据包里,另一种是用硬件时间捕捉引脚记录精确收发时刻。
第一种方式实现简单,但精度很差,因为 BLE 协议栈、操作系统调度、蓝牙芯片内部缓冲都会引入毫秒级的不确定性,而毫秒级的不确定性对应的是数百公里的距离误差,对防中继来说毫无意义。
第二种方式我在芯片选型时专门挑了一颗支持 RF 时间捕捉功能的蓝牙 SoC,它可以在射频前端检测到前导码的瞬间打上一个硬件时间戳,最大程度避免了协议栈调度干扰。实测下来,在室内环境往返测距时间可以从毫秒级降到微秒级,抖动范围控制在几微秒到几十微秒。
测距模块的实际逻辑是这样的:锁端发一个预设的测距包,钥匙端收到后立即原样返回。锁端记录发出时间 T1 和收到时间 T4,从数据包里解析出钥匙收到时间 T2 和回复时间 T3,则单程飞行时间大致等于:
Tflight = ((T4 - T1) - (T3 - T2)) / 2其中 (T3 - T2) 是钥匙端的处理延迟,需要从数据包字段中读出来。但如果钥匙端被攻击者控制,这个字段可能就是伪造的。所以整个方案里我始终坚持一个原则:硬件时间戳必须由密码学保护,不能被任意的普通数据包伪造。这也是为什么定制硬件要比纯手机 App 方案安全得多的原因。
3.4 综合评分决策:给每一层防护设置权重
单靠任何一个维度的数据都不足以做最终判断,我的做法是把时延测距、RSSI 特征、射频指纹、行为特征四项分别打分,再按加权系数合成一个最终安全评分。
我自己调试下来比较合理的权重配比是:RTT 测距结果权重最高(40%),射频指纹次之(30%),RSSI 特征(15%),行为特征(15%)。原因很简单:RTT 是最难伪造的物理量,中继链路每增加一米就必须多付出约 6.7 纳秒的纯信号飞行时间成本,再加上中继设备的转发处理延迟,这个放大效应非常明显。射频指纹的识别在小样本场景下容易漂移,所以权重不宜过高。
我也遇到过一种很刁钻的情况:攻击者把发射中继和真实钥匙放在了同一物理空间里,比如同一个房间、同一辆车上,这种情况下往返测距的时间和真实钥匙几乎一样。应对方案是加做射频指纹判断和行为特征判断——即使中继转发了全部数字逻辑,模拟前端始终是攻击者的硬件,指纹必然和初始配对时采集到的合法钥匙特征不一致。
3.5 一把锁的完整判定流程
整个解锁流程我在代码里实现成了一个状态机,核心判定逻辑可以用下面这段伪代码来概括:
function evaluate_unlock(session): if not verify_challenge_response(session): return DENY if session.counter <= last_counter[session.key_id]: return DENY score = 0.0 // RTT 测距 rtt_score = compute_rtt_score(session.average_rtt_us, session.rtt_measurements) score += 0.4 * rtt_score // 射频指纹 fingerprint_score = compute_fingerprint_score(session.rf_fingerprint) score += 0.3 * fingerprint_score // RSSI 行为特征 rssi_score = compute_rssi_behavior_score(session.rssi_measurements, session.rssi_variance) score += 0.15 * rssi_score // 行为特征 behavior_score = compute_behavior_score(session.time_since_first_contact, session.hour_of_day) score += 0.15 * behavior_score if score >= 85: return ALLOW else if score >= 60: return CHALLENGE // 升级认证 else: return DENYCHALLENGE是我特别喜欢的一个状态——如果只是有可能可疑但不完全确定,直接把门锁死了也不好,此时让用户走一次二次认证,比如在锁端输入 PIN 码。这样既不会把合法用户在恶劣环境中挡在门外,又能让自动化中继攻击无路可走。
3.6 参数配置参考:一版实测可用的配置表
下面这组参数是经过我上百次测试后确定的一套相对稳妥的默认配置,适合室内智能门锁场景,供你参考:
| 参数 | 推荐值 | 备注 |
|---|---|---|
| 最大允许 RTT | 400 微秒 | 对应约 60 米等效距离,留足余量 |
| RTT 抖动容忍度 | 平均值的 ±25% | 超过则该项评分直接减半 |
| RSSI 阈值 | -65 dBm 以上 | 低于该值默认距离不可信 |
| RSSI 方差最小值 | 0.5 dBm | 过小说明信号可能是被稳定放置的中继设备发射的 |
| 指纹匹配阈值 | 85% | 实测低于 80% 时可能是温度漂移,85% 为平衡点 |
| 连续失败封锁时间 | 30 秒 | 中继攻击者会因此大幅增加攻击耗时 |
| 升级认证触发线 | 安全评分 60-85 | 触发 PIN 码二次验证 |
| 测距次数 | 每次请求测量 5 次取中位数 | 有效过滤突发抖动干扰 |
需要注意,这套参数不是拿来就能直接用的,不同芯片、不同天线设计、不同安装环境(木门、铁门、玻璃门)对信号特性影响很大。上了现场之后必须先采集一组基线数据,再根据基线的均值和方差情况做调整。
4. 常见问题与排查技巧实录
4.1 低温环境下指纹匹配率骤降
冬天室外环境,我的测试设备出现过指纹匹配率从 90% 掉到 60% 以下的情况,一开始还以为算法有 bug,查了整整两天,最后用频谱仪对比不同温度下的信号特征才发现,温度对所使用晶振的频偏影响非常明显。解决思路是在指纹比对时加入温度补偿项——锁端增加一个温度传感器,用不同温度区间训练一套对应的指纹基线。如果你不想做那么复杂,最简单的方式是放宽指纹匹配阈值,同时把行为特征和人工确认的权重提高,牺牲一点自动通过率来保稳定性。
4.2 金属门框导致 RTT 测距波动
智能门锁装在金属防盗门上时,金属会反射甚至屏蔽部分射频信号,容易让多径效应变得更加严重。同一个位置的钥匙,RTT 测量值能在一秒内上下跳动几十微秒,如果不处理,中继防护机制会把这种情况误判成“中继链路抖动”。排查方法是用频谱仪或者手机 App 先看门框附近的 RSSI 多径分布,如果是金属环境,就把测距次数加大到 8 到 10 次,同时改用中位数而不是平均值作为最终判定量,抗干扰能力会好很多。
4.3 手机 App 作为钥匙的场景容易触发误报
手机方案最容易遇到的问题就是 RTT 极不稳定。BLE 芯片收到数据包到应用层响应之间存在太多不可预期的延迟,尤其是不同手机品牌、不同系统版本的蓝牙调度策略天差地别。我测试过一台低端安卓手机,处理一个往返响应需要 1 到 2 毫秒,折算成距离达到三百米以上。所以对于纯手机 App 钥匙,我建议不要启用严格的 RTT 测距,而是以 RSSI 渐变曲线 + 行为特征 + 用户 PIN 码作为主要防护手段。如果你实在想在手机方案里加入 RTT 辅助判断,建议把最大允许阈值放宽到 2 毫秒,而且只把它当作异常检测的辅助信号,别当成唯一依据。
4.4 多把钥匙同时使用时的指纹混淆
家里通常不止一把钥匙,几把钥匙放在同一张桌子上,同时被锁端扫描到时,信号会互相干扰,指纹提取可能发生混淆。我的解决方案是在协议层面给每把钥匙分配独立的物理信道偏移,或在时间上错开测距信号。每次建立的连接都严格绑定钥匙 ID,指纹特征按 ID 单独入库,绝不混合使用。
4.5 中继攻击的告警日志长什么样
为了验证中继攻击防护是否正常工作,我在日志系统里加了专门的攻击特征标记。一个典型的被拦截日志看起来是这样的:
{ "event": "unlock_rejected", "reason": "rtt_anomaly", "key_id": "ble_key_07", "average_rtt_us": 1280, "expected_rtt_us": 250, "rssi_dbm": -34, "rssi_variance": 0.2, "fingerprint_similarity": 0.92, "security_score": 31, "decision": "deny" }注意这里有个很微妙的地方:RSSI 显示信号极强(-34 dBm),指纹相似度也高达 92%,但 RTT 却是正常值的 5 倍。这就是典型的中继攻击特征——信号可以很强、指纹可以接近,但多出来的时间和处理链路是藏不住的。看到这种日志组合,基本可以判定是中继攻击。
4.6 一条超级实用的测试方法
最后分享一个我每次调完参数都要做的完整攻防测试流程。准备两台支持蓝牙抓包的设备,其中一台连上钥匙,另一台连上锁,然后用一个脚本把从钥匙端抓到的原始数据包实时转发到锁端设备,模拟完整的双向中继通路。这套设备搭建完,你可以反复调整中继链路的长度、路由器延迟、发射功率,观察锁端最终的表现。我的经验是:只要中继路径上多出 10 米网线或者一个 Wi-Fi 路由器转发,RTT 测距这一关大概率就能看出破绽,而指纹识别则能抓住那些特意把中继设备紧贴钥匙的情况。
我个人在这些测试里最大的体会是,防中继攻击不能指望某一个“银弹”式算法,越是底层的物理特征越可靠,但越稳定可靠的特征往往越难在普通硬件上获取。真正耐打的产品,一定是在芯片选型阶段就把安全能力考虑进去的,而不是等固件写完再打补丁。安全设计越靠前,成本越低,效果越好。如果你正在设计新产品的安全方案,我的建议是从 RTT 硬件时间戳和射频指纹采集这两个能力出发找芯片,远比你后期在协议层死磕要省力得多。