1. 先说结论:auth rejected 到底在告诉你什么事
调试 BLE 设备最怕的其实不是“扫描不到”或“连不上”,而是看似一切正常,数据也能收,结果你一点 NRF Connect 右上角那个盾牌图标,日志区直接弹出一行:bonding failed reason auth rejected。看到这行英文的人,第一反应一般是怀疑设备端蓝牙模块出了问题,第二反应是把代码里的安全参数翻一遍,第三反应才是回来重新读日志。实际上,这行日志的含义远比表面看起来要复杂。
auth rejected属于 GAP 安全流程里的“认证被拒绝”。它指的是在配对(Pairing)的认证环节,对端主动拒绝了你发过去的认证请求。这里的“对端”既可能是手机端的 NRF Connect,也可能是被调试的嵌入式设备。一旦出现这个错误,双方不会进入密钥分发阶段,也不会完成绑定,之后自然也就没法建立加密通信。
我在项目里遇到过至少四种导致这个错误的典型场景。第一,设备端安全参数里开启了很多要求,但手机端 NRF Connect 使用的配对配置恰好不满足。第二,设备端在收到安全请求后主动返回了拒绝响应。第三,同一个设备之前绑定过,旧绑定信息没清干净,二次绑定被策略拒绝。第四,两端的安全等级(比如 MITM、LESC、密钥长度)对不上,协商失败后被某一方视为拒绝。这篇文章我会从协议层开始讲,然后进入现场排查思路,最后给你一套可以直接照做的步骤,争取让你下次遇到这行字的时候不用再抓头。
提示:下面内容默认你用 NRF Connect 作为主机端调试工具,设备端基于 nRF 系列芯片或标准 BLE 协议栈开发。其他调试工具下核心排查思路同样适用,只是界面路径略有差异。
2. 先从协议层把概念捋清楚
2.1 配对不是绑定:三句话分清两个概念
BLE 技术中的“配对”是指双方经过一系列握手,建立加密连接的过程。整个过程分成三个阶段:安全参数交换、认证、密钥分发。第一阶段,双方交换配对特性和安全参数,包括输入输出能力、是否要求绑定、是否需要 MITM、是否支持 LESC、密钥长度范围等。第二阶段,根据这些参数发起认证,可以使用传统 PIN 输入、Just Works、Passkey 或数字比较等方式。第三阶段,在认证通过后分发长期密钥等关键材料。
而auth rejected发生在第二阶段的认证环节。也就是说,第一阶段参数已经交换完了,双方都已经知道对方要什么,但在真正执行认证时,有一方觉得条件不满足或者不想继续,于是返回拒绝。一个生活化的类比是:你和对方交换了名片、看了联系方式,但对方在真正握手的瞬间把手缩了回去——不是没联系方式的问题,是对方在“验明正身”这一步直接中止了。
2.2 绑定是在配对成功之后的动作
很多人把“配对”和“绑定”混着用,但在 BLE 协议里它们有明确区分。配对是一次性的加密建立过程,绑定则是配对完成后,把协商出来的长期密钥存储在本地,以便下次连接时直接恢复加密链路。用大白话说,配对是“这次认识”,绑定是“我记住了你,下次直接打招呼”。
NRF Connect 报错用的是bonding failed,但真正失败的点却在配对阶段,因为双方连最基本的认证都没通过,后续的密钥分发根本无从谈起,工具统一把这部分错误归类成绑定失败。这个细节很关键。排查时如果只盯着“绑定”两个字,很容易把注意力放到设备端的存储机制、Flash 管理和密钥保存上,结果查了一整晚,发现和存储半毛钱关系没有,纯粹是配对认证没通过。以后看到带auth的报错,先把精力放在安全握手参数上,绑定存储最后再查。
2.3 安全模式和级别:两端得在同一页上
BLE 里有一个“安全模式”和“安全级别”的组合概念,通常表述为 Security Mode 1 Level 1~4。Mode 1 表示加密模式,不同 Level 对应的能力如下:
| 安全级别 | 含义 | 典型使用场景 |
|---|---|---|
| Mode 1 Level 1 | 无加密 | 调试初期、无敏感数据 |
| Mode 1 Level 2 | 加密但不带 MITM | 普通数据保护,配对流程简单 |
| Mode 1 Level 3 | 加密且带 MITM | 需要防止中间人攻击 |
| Mode 1 Level 4 | LESC 加密且带 MITM | 高安全场景,如支付、医疗 |
NRF Connect 的日志和设置界面里,这些内容会以 Level 的形式出现。如果设备端配置的是 Level 2,而 NRF Connect 默认发起 Level 3,那么协商时两边的参数是矛盾的。设备端不希望有 MITM 要求,手机端却要求必须 MITM,双方在认证环节就可能直接撞车,出现auth rejected。只要两端的安全级别不一致,整套流程就很难顺利走完。设置参数之前,先明确产品最终要求的安全级别,再让两端的调试配置都向它靠齐。
2.4 IO 能力、MITM、LESC 三个参数的组合坑
配对时采用的认证方式由几项参数共同决定。IO Capability 代表设备支持的人机交互能力,比如键盘输入、屏幕显示、无交互,常见的枚举有 DisplayOnly、DisplayYesNo、KeyboardOnly、NoInputNoOutput、KeyboardDisplay。MITM 标志表示是否要求中间人攻击防护。LESC 标志表示是否使用低功耗安全连接,基于 ECDH 密钥交换,新版手机系统默认优先使用。
这三项的排列组合决定了配对时的交互流程。手机端是 KeyboardDisplay,设备端是 NoInputNoOutput,就走 Just Works,不需要用户确认;如果设备端是 KeyboardOnly,手机端是 DisplayOnly,通常走 Passkey 流程。最容易出问题的是 NoInputNoOutput 配上 MITM=true——设备既没有输入也没有输出,却要求防中间人攻击,这在协议层面没法实现,部分协议栈会在当前阶段直接返回拒绝,NRF Connect 里就表现为auth rejected。
提示:如果设备为了省成本用了 NoInputNoOutput,别急着改硬件,先把 MITM 改为 0 再验证。如果产品确实需要 MITM,那只能换硬件方案,加上屏幕或按键,否则这个安全检查在逻辑上就站不住。
3. 快速定位:先把设备端的安全参数翻出来
3.1 nRF5 SDK 下 sec_params 的正确写法
设备端的安全参数通常由一个ble_gap_sec_params_t结构体控制。以常见的 nRF5 SDK 工程为例,你会在连接事件或安全请求事件里填充并回复这个结构体:
static ble_gap_sec_params_t m_sec_params; static void gap_params_init(void) { memset(&m_sec_params, 0, sizeof(m_sec_params)); m_sec_params.timeout = 30; m_sec_params.bond = 1; m_sec_params.min_key_size = 7; m_sec_params.max_key_size = 16; m_sec_params.io_caps = BLE_GAP_IO_CAPS_NONE; m_sec_params.mitm = 1; m_sec_params.lesc = 1; ret_code = sd_ble_gap_sec_params_reply(conn_handle, BLE_GAP_SEC_STATUS_SUCCESS, &m_sec_params); APP_ERROR_CHECK(ret_code); }这段代码里,bond=1表示设备希望建立长期绑定,io_caps=NONE意味着没有输入输出,mitm=1却要求中间人防护。光看这三个值,你已经能嗅到问题——这是一个“要安全、但没能力做安全交互”的矛盾配置。很多模板工程默认不会打开 MITM,但如果你在某个版本里手改过,之后一直没动,那用 NRF Connect 调试时出现auth rejected的概率就非常高。
再注意min_key_size=7这个值。BLE 传统加密标准最小支持 7 字节密钥,如果你把最小值改成 16,而手机端协商时给出的密钥长度落在 7~15 之间,设备会直接拒绝。调试初期建议先用min_key_size=7, max_key_size=16,让链路先跑起来,最后再根据产品安全需求收紧。
3.2 新 SDK 环境下的安全配置差异
如果你用的不是老的 nRF5 SDK,而是新的 Zephyr 或 nRF Connect SDK,安全参数的配置方式会变化。新环境里一般在应用层配置,通过bt_conn_auth_cb注册回调,再用bt_conn_set_security发起或响应安全请求。认证回调里需要实现pair_accept、passkey_entry、passkey_confirm等函数,没有实现会让某些认证流程无法继续。
这也是一个容易踩的坑:老 SDK 迁移到新 SDK 时,原来只需填一个结构体,现在必须实现一串认证回调。如果你漏了pair_accept,系统就没有办法确认用户是否接受配对,部分情况下协议栈会自动返回拒绝,表现到手机端就是auth rejected。尤其是 LESC 的 Passkey 或数字比较流程,回调没实现基本等于自动否认一切。
3.3 NRF Connect 端到底在用什么参数发起配对
NRF Connect 在连接设备后,右上角有一个盾牌图标,点击后会发起安全连接。但在点击之前,你可以在软件设置里看到安全相关配置,里面通常包含几个选项:安全模式、是否要求绑定、MITM、LESC。不同操作系统版本的 NRF Connect 默认值不完全一样,有的版本默认 Level 2,有的版本默认 Level 3。
我在实际调试中发现,很多同事根本不知道这个默认配置的存在,全程默认值一路点下去。设备端要求 Level 3,工具却默认发 Level 2,或者反过来,结果就只剩下一行auth rejected。正确做法是:在点盾牌之前,先打开设置确认工具侧的安全参数,手动调到与设备一致,再触发配对。这一步看似简单,却能过滤掉至少三分之一我看到过的报错现场。
4. 实操排查:一套可以直接照做的排障流程
4.1 第一步:把日志完整保存下来,再看上下文
不要只盯着日志区最后一行。NRF Connect 在连接和配对过程中会打印 GATT 事件、连接事件、安全事件,在auth rejected之前,往往还有Security request received、Pairing requested、Encryption changed之类的前置日志。建议点盾牌之前就开启日志记录,触发失败后导出完整日志。
简单判断法:如果日志里连Pairing requested都没出现就直接报auth rejected,大概率是设备端在收到安全请求后立刻返回拒绝,重点查设备端安全事件处理代码。如果日志里出现了Pairing requested,但后面没有任何关于密钥交换或加密更新的记录,说明协商在认证阶段卡住,重点查两端安全参数是否匹配。
4.2 第二步:用最小化配置做对照试验
参数多了反而难排查,建议做一个“最小化对照试验”:把设备端安全参数临时调整为bond=1, io_caps=NONE, mitm=0, lesc=0,同时在 NRF Connect 里选择最低安全等级,然后重新连接并触发配对。
如果最宽松的配置能配对成功,就说明问题基本锁定在安全参数的组合上。之后逐个把mitm、lesc、io_caps加回来,用二分法快速定位是哪个参数触发了拒绝。这个方法类似于网络排查里的两端拉平策略——先用最简配置验证通路,再逐级加复杂条件。我在几乎每个项目里都用这个办法,基本不会跑偏。
4.3 第三步:看事件回调顺序,确认拒绝发生在哪一层
设备端收到安全请求,典型事件顺序是:BLE_GAP_EVT_SEC_PARAMS_REQUEST(或BLE_GAP_EVT_CONN_SEC_UPDATE),然后由你的代码响应;如果成功,后续收到密钥分发事件,最后进入加密状态。如果你在日志里看到设备端收到参数请求后没有任何下发的安全参数或响应记录,基本等于设备端在这条分支里走了 return,压根没给协议栈交代清楚。
建议在sd_ble_gap_sec_params_reply调用前后打印返回码。这里返回NRF_SUCCESS才说明协议栈接受了你传的安全参数。如果返回NRF_ERROR_INVALID_PARAM,多半是结构体里某个字段枚举值非法,比如io_caps传了不存在的枚举、key_size超出 7~16 范围。如果返回NRF_ERROR_INVALID_STATE,说明当前连接状态不允许在这个时机发送安全回复,常见于已经在加密状态但你又重复发起安全请求。
4.4 第四步:换一个主机端做交叉验证
还有一种特别快的区分办法:换主机端工具。比如你用手机端 NRF Connect 报auth rejected,可以换电脑端工具、换另一台手机,或者用另一块 nRF 开发板连接设备发起配对。如果另一台主机能配对上、原来的不行,多半是原来那台主机上的配置或系统蓝牙行为的问题;如果换任何主机都失败,问题百分之百在设备端。
这个方法能把“手机厂商的 BLE 实现差异”这个变量快速拎出来。我实际遇到过一些手机上配对正常、另一些手机配对失败的情况,就是典型的主机差异。如果手边没有第二块开发板,也可以用手机系统自带的蓝牙设置界面去配对,很多情况下系统蓝牙能配对、NRF Connect 却不能,问题就清晰指向工具配置了。
5. 常见问题速查表与几个隐蔽诱因
5.1 高频原因一览
| 现象 | 常见原因 | 快速验证方法 |
|---|---|---|
| 点盾牌后立刻报 auth rejected | 两端安全参数矛盾,或设备端 io_caps 与 mitm 冲突 | 设备端 mitm 改为 0,或从设备端发起安全请求 |
| 连接后过几秒才报 auth rejected | 旧绑定信息残留 | 删除两端绑定信息后重新连接,再做配对 |
| 同一设备首次成功、设备重启后失败 | 设备端绑定信息丢失或 Flash 异常 | 检查设备端绑定数据区和复位日志 |
| 换手机后出现错误 | 不同手机系统或不同 NRF Connect 版本默认安全配置不同 | 固定同一款手机作为基准,或手动校准安全参数 |
| 设备端日志显示收到参数请求但无响应 | 回调分支遗漏或提前 return,未调用 sec_params_reply | 补全回调,打印每个分支的执行状态 |
| 带 PIN 输入时报 auth rejected | 设备端 io_caps 与实际硬件交互能力不符 | 检查 IO 能力枚举值,确认硬件是否真的支持对应输入 |
5.2 看起来像认证问题、实际是缓存问题的隐藏分支
这一条我想单独拿出来说。很多场景下,设备端在开发过程中被反复刷固件,Flash 里的绑定记录可能被保留,也可能被清零。而手机端 NRF Connect 的绑定状态往往还残留在系统里。第二次连接时,手机端会尝试用旧长期密钥恢复加密链路,设备端面对不认识或不匹配的密钥,可能直接拒绝,此时界面不一定显示密钥不匹配,而是统一归纳成auth rejected一类错误。
解决办法很简单:把两端的历史绑定信息都清干净。手机上在系统蓝牙设置里移除该设备,设备端可以临时用一个强制擦除绑定区的用例,或者在初始化时配置为不恢复历史绑定,然后重新配对。做这一步只需要五分钟,但能筛掉“假认证问题”里很大一块。
5.3 业务代码里的“白名单”逻辑
还有一类容易被忽略的原因在业务代码里。有些设备端工程在收到安全事件时,会加入“只允许指定 MAC 或指定产品序号配对”的判断,不符合条件的设备直接返回拒绝。NRF Connect 端看到的同样是auth rejected,但实际问题在设备端业务逻辑,和 BLE 协议栈无关。
我在排查时会先去找设备端有没有类似的安全事件过滤分支。如果你自己加过这种逻辑,排查时先把它临时关掉,或者确认测试机的地址是否在白名单里。别小看这个场景,我见过有人在参数请求事件分支里写了一段向远端查询设备注册状态的代码,网络一慢或查询失败就直接返回拒绝,结果手机端怎么都配对不上。
6. 两个更现代的坑:LESC 与旧绑定信息冲突
6.1 手机全面默认 LESC 之后,老设备更难配对
现在的手机在系统层几乎默认支持并优先发起 LESC 配对。LESC 使用 ECDH 密钥交换和 AES-128 加密,安全性更好,但对设备端协议栈有版本要求。如果设备端固件较老、只支持传统配对,手机发起 LESC 参数后设备端无法理解或无法响应,一些协议栈会返回错误,表现就是auth rejected或直接断连。
排查时看一眼设备端芯片和 SDK 版本,确认是否支持 LESC。支持的话,确保安全参数里lesc=1,并且实现 LESC 相关回调。不支持的话,要么升级协议栈,要么在调试工具端显式关闭 LESC 走传统配对。这里要特别提醒:某些系统版本会强制使用 LESC,老设备在这种情况下如果协议栈兼容性不够,很容易出现不定期的配对失败。
6.2 旧绑定信息和新安全参数的冲突
当一个设备在旧版本固件下绑定过手机,固件升级后安全参数发生变化,比如从无 MITM 变成有 MITM,甚至从传统配对切到 LESC,那么旧绑定密钥和新的安全策略就出现了冲突。手机端尝试用旧密钥恢复链路,设备端却要求按新规则重新协商,两者无法收敛,最终结果仍然可能是一行auth rejected。
这类问题最好在固件升级说明里提前告知用户:升级后需要重新配对。设备端在检测到密钥版本或安全参数不匹配时,最好主动清除旧绑定数据并提示上层应用发起重新配对,而不是把这个脏状态留给每次连接去碰运气。做产品固件升级时,我习惯把安全参数版本号写进自定义服务里,客户端读取到版本变化后,主动触发重新绑定流程,省去大量用户侧重复反馈。
7. 一些平常文档里不会写的小经验
我的习惯是再急也先把安全参数列到纸上,画成一个两列表格对比两端,逐项核对。这个方法看起来土,但效率极高,因为auth rejected的根源几乎都是参数不匹配。你可以把表格列成:项、设备端配置、NRF Connect 配置、是否一致。排查前先填表,填完基本就能圈定范围了。
如果做完整轮排查依然复现,最后一道手段是把空中报文抓下来。用支持 BLE 嗅探的硬件抓包,可以看到 Security Request、Pairing Request、Pairing Response 里的具体标志位,也能看到第一个返回拒绝的报文到底从哪个方向发出。设备端发的还是手机端发的,这一点在界面上看不出,但在抓包数据里一目了然。能定位到方向之后,再回代码里翻对应方向的处理逻辑,疑难杂症基本都是这样收场的。
整个流程走下来,我最想强调的还是那句话:NRF Connect 的报错信息只是结论,不是原因。把注意力从“翻译这行英文”转移到“对比两端安全参数”上,问题才会真正浮出水面。这就是我在实际设备联调中反复用、也反复见效的一套打法,希望能帮你少走几步弯路。