简介:这份DOCX文档围绕无线路由器PIN码算法的破译与防护展开,面向网络安全初学者、无线网络运维人员及希望了解WPS风险的读者,帮助其在授权环境中理解PIN码推算思路与无线加密防护要点。压缩包仅含1个DOCX文件,大小约191KB,内容以图文讲解方式组织,便于直接阅读和检索关键概念。资源已有1185人学习下载,说明该主题在无线安全入门学习中具有一定关注度。文中结合扫描附近无线信号、获取MAC地址与SSID等信息、十六进制转十进制推算PIN码等案例,并讨论关闭WPS功能、启用WEP/WAP协议等防护措施,读者可从中获得PIN码算法原理、WPS后门风险认识以及基础无线网络安全加固思路,适合作为概念梳理与风险自查的参考材料。
1. 八位数字的错觉:无线路由器 PIN 码算法被攻破的从来不是加密
很多人第一次听到无线路由器 PIN 码算法被攻破,第一反应是 WPA2 不行了。事实恰好相反:这类事件里加密算法毫发无损,被撬开的是设备主动敞开的一条配置通道——WPS。更反直觉的是,PIN 只有八位十进制数,按一亿种组合硬猜要按月计,可实际在线交互次数被压到一万一千次上下,一台设备几个小时就能出结果。整个过程中没有一处依赖算力,全是协议在每一步把信息一点点漏给了对面。搞懂这条链路,比记住“关掉 WPS”这句结论有用得多,因为默认凭据、派生凭据、错误反馈这三样东西,今天仍然大量出现在各类设备的配网流程里。
2. WPS 协议栈:一个“让配置变简单”的设计如何造出可探测的身份通道
2.1 注册器与入网者:WPS 到底替谁做了决定
WPS 是 Wi-Fi 联盟在 2006 年前后推出来的东西,动机非常朴素:那个年代家用路由刚普及,让普通用户进后台选加密方式、敲一串十六进制 PSK,成功率极低。WPS 提供了两条路,一条是按键(PBC,按一下路由器上的按钮,两分钟内新设备自动入网),一条是 PIN(输入设备背面贴纸上的八位数字)。按键模式解决“没有键盘的设备”,PIN 模式解决“没有按钮的设备”,两条路一起覆盖了当年的消费级场景。
协议里有两个角色。Registrar 是“发放配置的一方”,负责把 SSID、加密方式、网络密钥打包下发给对方;Enrollee 是“申请入网的一方”,拿着自己的 Device Password 证明身份。关键点在于:出厂贴纸上的那八位 PIN,就是这台设备作为 Enrollee 时的 Device Password。它不是为了给用户登录后台用的,而是为了让一台外部 Registrar 能把配置推给它——设计初衷是“你买了新路由,用手机 App 扫一下贴纸 PIN,手机当 Registrar 把家里网络配好”。
这个角色设计埋下了第一个隐患:PIN 是一份长期有效的静态凭据,贴在设备外壳上,几年都不会变。它不随 Wi-Fi 密码修改而失效,也不随固件升级而轮换。用安全工程的视角看,这相当于在 WPA2 那把锁旁边,永久性地留了一把挂锁,钥匙还印在门牌上。
第二个隐患是发现机制。为了让人知道“我这台设备支持 WPS”,AP 会在 Beacon 帧和 Probe Response 帧里挂一个厂商自定义信息元素(Element ID 0xDD,OUI 00:50:F2,Type 0x04),里面带着 WPS 版本(属性 0x104A)、设备名(0x1011)、配置方法(0x1008)、以及 Device Password ID(0x1012)。Device Password ID 取 0x0000 表示“默认 PIN 模式可用”,取 0x0004 表示“按键模式”。也就是说,一台开启了 PIN 模式的路由器,会在空气里持续广播“我这里可以用贴纸 PIN 配网”。这是黑匣子被打开的第一道缝——不需要认证,不需要关联,扫一圈就全知道了。
提示:这些属性在 WPS 规范里属于“设备信息”,本意是方便用户识别该连哪台设备、该用哪种方式配网,不是机密字段。设计上它被当作公开信息处理,问题出在它与后面的静态凭据绑在了一起。
2.2 EAP-WSC 的八条消息:M1 到 M8 里泄露了什么
WPS 的配网会话跑在 EAP 之上,具体叫 EAP-WSC,封装在 EAPOL 帧里(EtherType 0x888E)。整个握手是八条消息,M1 到 M8,双方用 Diffie-Hellman 交换出一个共享密钥,再用 PIN 派生的密钥去保护两个随机数 R-S1 和 R-S2。
流程大致是这样:
- M1(Enrollee → Registrar)携带 Enrollee Nonce、可选的 DH 公钥、厂商信息;
- M2(Registrar → Enrollee)回 Registrar Nonce 和 DH 公钥;
- M3 确认双方公钥,共享密钥成型;
- M4(Registrar → Enrollee)发送 R-S1,用 PIN 派生的密钥加密;
- M5(Enrollee → Registrar)回 E-S1,同样用 PIN 派生的密钥加密;
- M6(Registrar → Enrollee)发送 R-S2;
- M7(Enrollee → Registrar)回 E-S2;
- M8(Registrar → Enrollee)收尾,下发配置。
真正的要害在密钥派生的粒度上。规范没有用完整的八位 PIN 去派生一把密钥,而是把它切成两段:PIN 的前四位参与派生保护 M4/M5 的密钥,后四位参与派生保护 M6/M7 的密钥。这种切分是有实现层面的考虑的——它允许 Registrar 先验证前四位、再验证后四位,输入错了只需要重输一段;也让一些没有完整键盘的设备可以分两次录入。用可用性换复杂度,看起来划算。
代价是:验证被拆成了两次独立的判定,而每次判定都会产生一个明确的结果信号。这个信号就是 EAP-NACK,WSC 里叫 WSC_NACK。当 Enrollee 解不开 M4 里的 R-S1 时,它会在 M5 的位置回一个 NACK;解开了但 M6 里的 R-S2 对不上,就在 M7 的位置回一个 NACK。对端收到 NACK,立刻知道“这一段错了”。
到这里,问题就从“猜一个八位数”变成了“猜一个四位数,再猜一个四位数”。而且这两段是被分开反馈的,可以独立穷举。协议没有做任何节流设计的补偿:规范里有一个 AP Setup Locked 属性(0x1057),本意是“配置流程进行中,其他设备不要来抢”,很多实现把它做成了软状态,超时或重启就清空,起不到限速作用。
3. 校验位与拆半验证:一亿种组合是怎么塌缩到一万一千次的
3.1 最后一位不是密钥,是校验和
八位 PIN 里,第八位不携带任何熵。它是前七位的校验和,作用类似身份证号最后一位,纯粹为了在手输的时候挡住“按错一个键”。算法在多个开源实现里都能看到,思路是从最低位往上,按 3、1 交替加权求和,再对 10 取补。
def wps_checksum(pin7: int) -> int: """WPS PIN 第 8 位:前 7 位的校验和。 从最低有效位开始,权重按 3、1 交替,最后对 10 取补。 """ accum = 0 n = pin7 while n: accum += 3 * (n % 10) # 当前最低位,权重 3 n //= 10 accum += n % 10 # 次低位,权重 1 n //= 10 return (10 - accum % 10) % 10 # 取补后仍需再模 10,处理余数为 0 的情况 # 校验:7 位候选 5995737,加权和 101,余 1,补 9 print(wps_checksum(5995737)) # 输出 9 print(f"{5995737}{wps_checksum(5995737)}") # 完整 PIN:59957379这段代码里两个参数值得说明。pin7是去掉校验位后的七位整数,允许前导零丢失,所以实际使用时通常用字符串补齐到七位再转整数。accum的加权方向是从个位开始,这一点容易写反——如果从最高位开始加权,算出来的校验位会对不上,表现出来的现象是“算出来的 PIN 永远是错的”,排查时优先怀疑这里。
校验位带来的安全后果是:有效搜索空间从 10^8 降到 10^7。这本身不算致命,一千万次在线交互还是不可行。真正致命的是下一节。
3.2 EAP-NACK 把“错在哪一半”告诉了对方
把 3.1 的结论和 2.2 的拆半验证合起来看:前四位独立验证,后四位独立验证,而最后一位由前七位决定、不需要猜。于是,穷举前四位的代价是 10^4 次,穷举后四位(三位有效数字加一位算出来的校验位)的代价是 10^3 次。
def search_space(): """拆半验证下的实际尝试次数""" first = 10 ** 4 # PIN 前 4 位:0000 - 9999 second = 10 ** 3 # 后 4 位中只有 3 位可猜,第 8 位由校验位定死 worst = first + second # 最坏情况 11000 次 avg = first // 2 + second // 2 # 平均情况 5500 次 return worst, avg worst, avg = search_space() print(worst, avg) # 11000 5500参数含义很直白:first是前四位全空间,second是后四位去掉校验位后的空间。最坏一万一千次,平均五千五百次。若一次 EAP-WSC 会话从发起到收到 NACK 平均耗时 0.5 到 2 秒(取决于 AP 的状态机实现、信号质量和重传策略),把这个区间代进去,总耗时落在 45 分钟到 6 小时之间。原始事件里说的“两小时”,正好在这个区间的中段,属于合理估算而不是夸张。
值得注意的是,协议本身并没有“故意”泄露位置信息。EAP-NACK 是 EAP 框架的标准语义,它要告诉对端“这次协商失败,请重来”,而重来的粒度就是当前这段密钥。设计者把 PIN 切成两段,是为了让验证可以分段进行;分段验证必然产生分段反馈;分段反馈必然把搜索空间从乘法变成加法。这是一条很典型的因果链:每一个单独看都合理的决定,串起来就成了漏洞。
注意:很多人把这类攻击理解成“算力破解”,于是下意识觉得换更强的加密、加更长的密码就能挡住。实际上被攻击的是配网阶段的身份验证通道,跟空口加密强度没有关系。理解这一点,才不会在错误的方向上加固。
4. 从 MAC 到 PIN:产线便利性如何变成一把批量钥匙
4.1 默认 PIN 的推导链条:取模、截断、补校验位
如果说拆半验证把成本从“月”降到“小时”,那么默认 PIN 派生把成本从“小时”降到“一次”。事情的起因在生产环节:每台设备烧一个真随机 PIN,意味着要在产线上生成随机数、写进固件、再同步到外壳贴纸的打印系统,三处必须严格对齐,任何一处错位都会导致用户拿到一台“贴纸 PIN 与固件不符”的机器。这在售后是灾难。于是有一部分批次的固件选择了另一条路——用 MAC 地址派生 PIN,让贴纸打印机和固件用同一套规则各自算一遍,永远不会对不上。
推导形态大致如下:
def pin_from_mac(mac: str) -> str: """默认 PIN 派生的一种历史形态: 取 MAC 尾部 6 个十六进制字符 -> 转十进制 -> 截断到 7 位 -> 补校验位 """ tail = mac.replace(":", "").replace("-", "").upper()[-6:] n = int(tail, 16) # 十六进制转十进制 cand = n % 10_000_000 # 截断到 7 位,不同固件取模位数不一样 cand_str = str(cand).zfill(7) # 前导零补齐,否则校验位会算错 return cand_str + str(wps_checksum(int(cand_str))) demo = "02:1F:3A:5B:7C:D9" # 虚构地址,尾部 5B7CD9 = 5995737 print(int("5B7CD9", 16)) # 5995737 print(pin_from_mac(demo)) # 59957379两个参数要盯住。[-6:]取的是 MAC 尾部六个十六进制字符,二十四位地址去掉 OUI 前缀后的设备部分,正好六个字符,这是最常见的取法。% 10_000_000是截断方式,不同批次固件有的取模十的七次方,有的直接取十进制字符串的低七位,还有的只取六位。原始事件里那台设备算出来的350936只有六位数字,这就是取六位变体的证据——它只给了 PIN 的前六位,剩下一位加校验位靠猜,所以原文才说“最后一位数字只要猜十次”。这个细节说明:默认 PIN 派生没有统一标准,它是一堆厂商各自实现的集合,形态随批次漂移。
安全后果非常直接:MAC 地址是公开的。它在 Beacon 帧的地址字段里,在每一次 Probe Request 里,在 ARP 表里,在任何一台能收到信号的设备上都能读出来。把一个公开值经过固定算法变换一次,得到的仍然是公开信息。PIN 的保密性在这里被完全消解,攻击者不需要在线交互,扫到 MAC 就能离线算出 PIN,然后一次性走完 WPS 绑定流程。原文说“涉及的 MAC 地址段不多”,指的就是只有特定 OUI 前缀的批次用了这套派生,其余设备仍然要靠拆半穷举。
4.2 弱随机数:另一条把在线交互压成离线的路径
默认 PIN 派生依赖“厂商用了确定性算法”,这属于实现缺陷,覆盖面有限。后续公开研究里还出现过另一类问题,根子在随机数生成器上,覆盖面更广。
回顾一下 M4 到 M7 的结构:R-S1 和 E-S1 是双方各自生成的随机 secret,它们被 PIN 派生的密钥包起来传输。如果 Enrollee 生成 E-S1、E-S2 时用的是低熵随机源——比如以开机时长、系统时钟的低位做种子,或者直接用了一个被固定种子的伪随机函数——那么这两个 secret 的取值空间会小到可以枚举。攻击者只要抓到 M1/M3 里的 Nonce 和 M5/M7 里的密文,就可以离线把种子空间跑一遍,对每个候选种子重算 secret、重算密钥、尝试解密,能解开的那个种子就锁定了 PIN。
| 攻击路径 | 依赖条件 | 交互次数 | 耗时量级 |
|---|---|---|---|
| 拆半在线穷举 | 仅需 PIN 模式开启 | 约 11000 次 | 小时级 |
| 默认 PIN 派生 | 特定批次固件 | 1 次 | 秒级 |
| 弱随机数离线枚举 | 随机源熵不足 | 1 次捕获 | 分钟到小时级 |
这张表想说明的是同一个结构性事实:WPS 的安全性完全建立在“PIN 是秘密”这个假设上。一旦 PIN 可以从 MAC 推出来,或者保护 PIN 的随机数可以被枚举,整个机制的信任根基就没了。协议层再加固也没用,因为泄露发生在协议之下的实现层。
5. 排查与避坑:为什么“已经关了 WPS”设备仍在应答
5.1 五个被反复误读的现象
现象一:后台里把 WPS 关掉了,扫描工具还是能看到这台设备带 WPS 信息。原因多半是界面上的开关只关掉了按键模式,PIN 模式仍在 WSC 状态机里注册着。这两条路在实现里经常是两个独立配置项,UI 只暴露了其中一个。解决办法是回到 Beacon 帧里看 Device Password ID 字段:如果它仍然是 0x0000,说明默认 PIN 模式还活着,界面上那个开关没有真的生效。
现象二:隐藏 SSID、开 MAC 白名单,就觉得安全了。隐藏 SSID 只是不广播 SSID 字段,Probe Response 里该带的 WSC 信息元素一个不少,设备名、型号、配置方法全都还在。MAC 白名单的作用点在关联阶段,而 WPS 的信息暴露发生在关联之前。这两项措施对配网通道的暴露面没有任何收缩作用,属于典型的“加固了错误的那一层”。
现象三:“我 WPA2 密码设了二十多位,怎么还被破了”。因为对方根本没有碰四次握手。WPA2 的强度保护的是数据面,WPS 保护的是配置面,两者是并列的两扇门。配置面被打开之后,网络密钥是被“下发”出来的,不是被“算”出来的,密码再长也不参与这个过程。这个误解在实际排查里出现频率最高,也最容易让人得出错误结论。
现象四:PIN 试错几次之后设备好像锁了,等一会儿又活了。这类锁定通常是软件层的计数器,存在内存里,超时清零或者重启清零。考虑到拆半验证只需要在后半段成功一次就能收敛,这种量级的节流对整体耗时的影响很有限。把限速当成主要防线,是血泪经验里最常见的一条弯路。
现象五:双频路由器只关了一个频段的 WPS。2.4G 和 5G 在部分固件里是两个独立的无线实例,各自跑一套 WSC 状态机,配置项也是分开的。关掉 2.4G 那个,5G 那个照样在 Beacon 里挂着 WPS 信息元素。验证时要把两个频段分别扫一遍,别只看一个就下结论。
5.2 验证动作:从 Beacon 里的 WSC IE 开始
判断一台设备是否真的关干净了,最可靠的办法不是看后台界面,而是看空口上还在广播什么。思路是抓一小段 Beacon 和 Probe Response,解析里面的厂商自定义信息元素,确认三件事:WSC IE 是否还存在、Device Password ID 的取值是什么、AP Setup Locked 是否被置位。
# 伪代码:从 Beacon 帧的 information elements 里筛出 WSC IE def find_wsc_ie(ies: bytes): i = 0 while i + 2 <= len(ies): eid, elen = ies[i], ies[i + 1] body = ies[i + 2: i + 2 + elen] # Element ID 0xDD 是厂商自定义,OUI 00:50:F2 + Type 0x04 才是 WSC if eid == 0xDD and body[:4] == b"\x00\x50\xf2\x04": return body[4:] i += 2 + elen return None # 返回非 None 就说明这台设备仍在广播 WPS 能力这段代码只做一件事:在信息元素列表里按(Element ID, Length, Body)三元组顺序遍历,命中 OUI 为00:50:F2、类型为0x04的那一条就返回。参数上要注意长度字段是单字节,最大 255,遍历时的步进必须是2 + elen,少加这个 2 就会在第二个元素处错位,读出来的全是垃圾数据——这是自己写解析器时最容易翻车的地方。
拿到 WSC IE 之后,再往里找属性 0x1012。取值为 0x0000 表示默认 PIN 可用,这时候真正有效的动作只有两个:在固件里彻底关掉 PIN 模式(不是关按钮),或者换一台允许你关干净的设备。如果固件压根没提供这个选项,那这台设备在网络边界上就应该被当成一台“配置面不可信”的设备来处理,比如只让它做纯 AP、把管理面隔离出去、或者干脆降级成访客网络用。
6. 设计取舍:把可用性放进安全通道要付多少利息
6.1 默认值与派生凭据的原罪
回头看这条链路,真正值得记住的不是某个算法,而是三个反复出现的模式。第一,静态默认凭据一旦贴在外壳上,就再也收不回来了,它不随密码轮换、不随固件升级失效,生命周期等于设备寿命。第二,把凭据从别的公开值派生出来,无论算法多复杂,本质上都是把秘密降级成公开信息,复杂度只是让人一时看不出这层等价关系。第三,为了改善体验而做的分段验证、分步反馈,会把搜索空间从乘法变成加法,每一段独立可破。
这三条不是 WPS 独有的。今天去看各种设备的配网流程——二维码配网、声波配网、蓝牙辅助配网——都能看到同构的设计:为了降低首次配置的门槛,协议在短时间内打开一个宽松的通道,通道里传递的凭据往往是长期有效的。区别只在于通道开了多久、凭据能不能轮换、失败反馈泄露了多少信息。
6.2 一套可复现的自检清单
在自己能控制的设备上,我会按这个顺序过一遍:先确认空口上还有没有 WSC IE,再确认 Device Password ID 是不是 0x0000,两个频段分别看;然后确认后台那个 WPS 开关关掉之后,上面的扫描结果有没有变化;最后确认固件版本,早期实现里 PIN 模式常常没有独立开关。这三步做完,基本能判断一台设备是“真的关了”还是“看起来关了”。
| 检查项 | 通过标准 | 不通过时的动作 |
|---|---|---|
| Beacon 中 WSC IE | 不存在 | 关 PIN 模式或更换固件 |
| Device Password ID | 非 0x0000 | 同上,注意双频段分别检查 |
| 后台开关与空口一致性 | 关闭后扫描结果同步消失 | 视为 UI 未生效,按未关闭处理 |
| 固件是否提供 PIN 独立开关 | 提供 | 无开关的老设备按不可信处理 |
这套清单花不了十分钟,但它挡住的是最容易被忽略的那类风险:你以为关了,实际没关。
从那以后我每次碰任何带配网功能的设备,都会先扫一遍空口再动配置,先把“它自己说了什么”记下来,再去看“界面说它是什么”。这个习惯救过我好几次,包括一些自认为已经把开关关干净的场景。希望帮到你。
本文还有配套的精品资源,点击获取