<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>网络笔记 on Bruce's 博客</title><link>https://www.huangqixuan.com/categories/%E7%BD%91%E7%BB%9C%E7%AC%94%E8%AE%B0/</link><description>Recent content in 网络笔记 on Bruce's 博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 28 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://www.huangqixuan.com/categories/%E7%BD%91%E7%BB%9C%E7%AC%94%E8%AE%B0/index.xml" rel="self" type="application/rss+xml"/><item><title>BNBU 校园 VPN 一键连接：OpenConnect、AnyConnect 与宁盾令牌 TOTP 教程</title><link>https://www.huangqixuan.com/p/research-anyconnect-bnbu/</link><pubDate>Fri, 28 Aug 2026 00:00:00 +0800</pubDate><guid>https://www.huangqixuan.com/p/research-anyconnect-bnbu/</guid><description>&lt;img src="https://www.huangqixuan.com/img/anyconnect/anyconnect-technical-cover.png" alt="Featured image of post BNBU 校园 VPN 一键连接：OpenConnect、AnyConnect 与宁盾令牌 TOTP 教程" /&gt;&lt;p&gt;我做这个项目，没有什么宏大的理由，只是每次连学校 VPN 都觉得麻烦。&lt;/p&gt;&#10;&lt;p&gt;以前打开 Cisco AnyConnect，要输入账号、密码，再拿出手机，打开宁盾令牌看六位验证码。有时宁盾令牌自己还要先输入安全锁密码。一套流程走完并不久，但每次都重复，次数多了就很烦。&lt;/p&gt;&#10;&lt;p&gt;原版 Cisco AnyConnect 还会安装后台服务，并随 Windows 启动。这个设计对企业设备可能很正常，对我却是一直挂着一个平时用不到的服务。我实在忍受不了，才开始找替代客户端。&lt;/p&gt;&#10;&lt;p&gt;最后的效果很简单：打开 OpenConnect-GUI，选中 &lt;code&gt;BNBU VPN&lt;/code&gt;，点击 Connect。账号、静态密码和动态码都由客户端处理，不需要再拿手机。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="最终的一次点击连接界面" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.huangqixuan.com/img/anyconnect/openconnect-one-click.png"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;现在连接 BNBU VPN 只剩一次点击。&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;这篇先讲我为什么一路折腾到这里，再给出一套可以照着做的完整流程。教程只适用于你本人有权使用的账号和学校发给你的个人令牌。二维码、seed、Base32 secret 和完整 &lt;code&gt;otpauth://&lt;/code&gt; URI 都是登录凭据，不要发给别人，也不要上传到在线转换网站。&lt;/p&gt;&#10;&lt;h2 id="四篇文章给我的启发"&gt;&lt;a href="#%e5%9b%9b%e7%af%87%e6%96%87%e7%ab%a0%e7%bb%99%e6%88%91%e7%9a%84%e5%90%af%e5%8f%91" class="header-anchor"&gt;&lt;/a&gt;四篇文章给我的启发&#10;&lt;/h2&gt;&lt;p&gt;我不是从零猜出 OpenConnect 和宁盾令牌的用法。真正让我把思路串起来的是四篇博客：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://haotian22.top/546c6c89.html" target="_blank" rel="noopener"&#10; &gt;HaoTian：UIC AnyConnect 的校园 VPN 研究&lt;/a&gt;，写于 2024 年。它从 Clash DNS、路由表、局域网和 ZeroTier 冲突一路研究到 OpenConnect-GUI，让我第一次意识到 Cisco AnyConnect 客户端本身也可以替换。文中的 &lt;code&gt;v.uic.edu.cn&lt;/code&gt; 属于当时的 UIC 环境，当前 BNBU 配置应使用实际可用的 &lt;code&gt;v.bnbu.edu.cn&lt;/code&gt;。&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://baka.link/zh/posts/dkey-token-to-standard-totp/" target="_blank" rel="noopener"&#10; &gt;Kirin：将 Dkey Token（宁盾令牌）转换为标准 TOTP&lt;/a&gt;，写于 2022 年。它展示了宁盾 App 激活时下发的 &lt;code&gt;seed&lt;/code&gt;、&lt;code&gt;timeStep&lt;/code&gt; 和 &lt;code&gt;passwordLength&lt;/code&gt;，并说明 Hex seed 可以转换成 Base32。&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://blog.steven53.top/p/%E5%AE%81%E7%9B%BE%E4%BB%A4%E7%89%8Cdkey-token%E8%BD%AC%E6%8D%A2%E4%B8%BA%E6%A0%87%E5%87%86totp/" target="_blank" rel="noopener"&#10; &gt;Steven：宁盾令牌（Dkey Token）转换为标准 TOTP&lt;/a&gt;，写于 2023 年。它同时记录了网页响应和二维码两条路线，还解释了 Base64 seed、Hex seed 与 Base32 secret 其实是同一串二进制密钥的不同表示。&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.jianyun.run/posts/convert-ningdun-token-to-standard-2fa-totp/" target="_blank" rel="noopener"&#10; &gt;唐长老：宁盾令牌转标准 2FA TOTP&lt;/a&gt;，写于 2020 年。它把二维码里的 token、Hex 到 Base32 的转换和 &lt;code&gt;otpauth://&lt;/code&gt; URI 结构连成了一条完整链路。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;第一篇解决的是“为什么换客户端”和网络协同，后面三篇解决的是“怎样让宁盾验证码自动生成”。它们的学校、年份和客户端环境不完全相同，不能逐项照抄；放在一起之后，OpenConnect 替代、TOTP 参数和 BNBU 实测才组成一条完整路线。&lt;/p&gt;&#10;&lt;h2 id="整个项目是怎样走弯路的"&gt;&lt;a href="#%e6%95%b4%e4%b8%aa%e9%a1%b9%e7%9b%ae%e6%98%af%e6%80%8e%e6%a0%b7%e8%b5%b0%e5%bc%af%e8%b7%af%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;整个项目是怎样走弯路的&#10;&lt;/h2&gt;&lt;p&gt;我先确认 OpenConnect 能不能代替 Cisco AnyConnect。OpenConnect-GUI 可以连接 BNBU，登录窗口也能正确显示 Student 认证组，但当时仍要输入密码和宁盾验证码。&lt;/p&gt;&#10;&lt;p&gt;接着我试过命令行脚本。脚本能提交账号和静态密码，也能进入：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Please enter a dynamic token&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可它提交动态码后总是 &lt;code&gt;Login failed&lt;/code&gt;，同一时间用 GUI 却能成功。这说明密码不是问题，失败发生在宁盾挑战的交互方式。继续堆脚本只会增加登录失败次数，所以我停掉了命令行路线。&lt;/p&gt;&#10;&lt;p&gt;后来查看 OpenConnect-GUI 的行为，才确认 &lt;code&gt;Batch mode&lt;/code&gt; 可以保存静态密码。这样已经能少输一项，但动态码仍要拿手机看。&lt;/p&gt;&#10;&lt;p&gt;最后卡住的是宁盾令牌。最初拿到的公共激活二维码只有服务器信息，没有个人 token。我一度准备抓宁盾 App 的激活流量。后来从 BNBU 门户重新获取个人手机令牌，才发现新二维码属于另一种结构，里面直接有个人 token，不需要抓包。&lt;/p&gt;&#10;&lt;p&gt;ChatGPT 完成了 OpenConnect-GUI 配置、认证流程排查和 OpenConnect 9.21 升级，但在个人令牌转换这里停了下来。我只把宁盾令牌这一段交给 DeepSeek harness 继续处理。它调用本机离线二维码库读取我自己的二维码，检查字段结构，再完成 Hex 到 Base32 的转换。最终验收仍然是官方宁盾 App 对码和真实 VPN 连接，不是模型说“应该可以”。&lt;/p&gt;&#10;&lt;h2 id="准备工作与风险"&gt;&lt;a href="#%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c%e4%b8%8e%e9%a3%8e%e9%99%a9" class="header-anchor"&gt;&lt;/a&gt;准备工作与风险&#10;&lt;/h2&gt;&lt;p&gt;开始前先确认这些条件：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;你能正常登录 &lt;code&gt;portal.bnbu.edu.cn&lt;/code&gt;。&lt;/li&gt;&#10;&lt;li&gt;你自己的 BNBU VPN 账号当前可用。&lt;/li&gt;&#10;&lt;li&gt;手机里的宁盾令牌当前能生成正确动态码。&lt;/li&gt;&#10;&lt;li&gt;Windows 自动设置时间和时区已经开启。&lt;/li&gt;&#10;&lt;li&gt;你接受把静态密码与第二因素放在同一台电脑上的风险。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;重新获取手机令牌可能让旧令牌立即失效。先留出足够时间，一次做完重新绑定、转换、对码和 VPN 测试。不要在快要用 VPN 时临时操作。&lt;/p&gt;&#10;&lt;h2 id="第一步安装并验证-openconnect-gui"&gt;&lt;a href="#%e7%ac%ac%e4%b8%80%e6%ad%a5%e5%ae%89%e8%a3%85%e5%b9%b6%e9%aa%8c%e8%af%81-openconnect-gui" class="header-anchor"&gt;&lt;/a&gt;第一步：安装并验证 OpenConnect-GUI&#10;&lt;/h2&gt;&lt;p&gt;安装 OpenConnect-GUI 后，先不要碰 OTP。只配置服务器并完成一次普通连接，确认替代客户端本身可用。&lt;/p&gt;&#10;&lt;p&gt;基础项目如下：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;项目&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;填写内容&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Name&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;BNBU VPN&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gateway&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;https://v.bnbu.edu.cn&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Username&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;你的 BNBU 账号&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Groupname&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;student&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;VPN protocol&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Cisco AnyConnect or OpenConnect&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;CA Certificate&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;留空，使用系统证书&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Server Certificate&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;留空&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;User Certificate / User Key&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;留空&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Disable UDP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;默认不勾&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Use Proxy&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不勾&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;第一次测试时，把 OTP Token 设为 None 或 Disabled。连接窗口出现认证组时选择 Student，输入账号、密码和手机上的宁盾六位码。只有这一步成功，后面才有继续自动化的意义。&lt;/p&gt;&#10;&lt;h2 id="第二步确认底层-openconnect-至少是-920"&gt;&lt;a href="#%e7%ac%ac%e4%ba%8c%e6%ad%a5%e7%a1%ae%e8%ae%a4%e5%ba%95%e5%b1%82-openconnect-%e8%87%b3%e5%b0%91%e6%98%af-920" class="header-anchor"&gt;&lt;/a&gt;第二步：确认底层 OpenConnect 至少是 9.20&#10;&lt;/h2&gt;&lt;p&gt;OpenConnect-GUI 1.6.2 当时附带的底层核心是 9.12。这个版本不能完整解析 &lt;code&gt;otpauth://&lt;/code&gt; URI，也不能从 URI 正确读取非默认的 60 秒周期。&lt;/p&gt;&#10;&lt;p&gt;&lt;a class="link" href="https://www.infradead.org/openconnect/changelog.html" target="_blank" rel="noopener"&#10; &gt;OpenConnect 更新记录&lt;/a&gt;显示，&lt;code&gt;otpauth://&lt;/code&gt; URI 和非默认 TOTP 周期支持在 9.20 加入。我实际使用的是 9.21，GnuTLS 版本为 3.8.11。&lt;/p&gt;&#10;&lt;p&gt;在 PowerShell 中检查：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C:\Program Files\OpenConnect-GUI\openconnect.exe&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;-version&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;应该看到类似：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;OpenConnect version v9.21&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Using GnuTLS 3.8.11&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Features present: ... HOTP software token, TOTP software token ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果还是 9.12，不要只复制一个来路不明的 DLL。OpenConnect、GnuTLS 及依赖库要使用同一套 Windows 构建。优先使用 OpenConnect 官方发布页或可信发行包，并保留能够恢复的旧安装。升级后先启动 GUI，再重新做一次手动验证码连接。&lt;/p&gt;&#10;&lt;h2 id="第三步从-bnbu-门户进入令牌解绑"&gt;&lt;a href="#%e7%ac%ac%e4%b8%89%e6%ad%a5%e4%bb%8e-bnbu-%e9%97%a8%e6%88%b7%e8%bf%9b%e5%85%a5%e4%bb%a4%e7%89%8c%e8%a7%a3%e7%bb%91" class="header-anchor"&gt;&lt;/a&gt;第三步：从 BNBU 门户进入令牌解绑&#10;&lt;/h2&gt;&lt;p&gt;登录 &lt;a class="link" href="https://portal.bnbu.edu.cn/" target="_blank" rel="noopener"&#10; &gt;BNBU Portal&lt;/a&gt;，进入应用中心，在通用服务中找到“令牌解绑”。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="BNBU Portal 中的令牌解绑入口" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.huangqixuan.com/img/anyconnect/bnbu-portal-token-service.png"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;图中的“令牌解绑”就是进入宁盾自助页面的入口。&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;进入后选择“手机令牌”。页面会显示当前令牌状态，并提供“重新获取手机令牌”和“显示激活码内容”。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="宁盾手机令牌自助页面" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.huangqixuan.com/img/anyconnect/nington-mobile-token.png"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;点击重新获取后，以新页面生成的个人二维码为准。图中二维码区域已经留空。&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;公共激活二维码与这里重新生成的个人二维码可能不同：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;类型&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;解码后的特征&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;应该怎样处理&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;公共激活码&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;activationMethod: 2&lt;/code&gt;，没有 &lt;code&gt;token&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不能直接转换，需要走官方 App 激活流程&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;个人手机令牌&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;activationMethod: 1&lt;/code&gt;，包含 &lt;code&gt;token&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;可以在本人电脑上离线检查和转换&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;BNBU 这次实测走的是第二种。个人二维码中的 token 带有 Hex seed、算法编号、刷新周期和验证码长度。&lt;/p&gt;&#10;&lt;h2 id="第四步完全离线地读取和转换"&gt;&lt;a href="#%e7%ac%ac%e5%9b%9b%e6%ad%a5%e5%ae%8c%e5%85%a8%e7%a6%bb%e7%ba%bf%e5%9c%b0%e8%af%bb%e5%8f%96%e5%92%8c%e8%bd%ac%e6%8d%a2" class="header-anchor"&gt;&lt;/a&gt;第四步：完全离线地读取和转换&#10;&lt;/h2&gt;&lt;p&gt;不要把个人二维码传给在线二维码网站。最方便的入口是宁盾页面的“显示激活码内容”，复制完整激活链接后，在本人电脑上处理。&lt;/p&gt;&#10;&lt;p&gt;下面脚本只使用 Python 标准库。它会读取粘贴的激活链接，解析 &lt;code&gt;#&lt;/code&gt; 后的 Base64 JSON，检查是否包含个人 token，再把 Hex seed 转为 Base32 并生成 &lt;code&gt;otpauth://&lt;/code&gt; URI。&lt;/p&gt;&#10;&lt;p&gt;新建 &lt;code&gt;convert_dkey.py&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlsplit&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;粘贴个人手机令牌的完整激活链接：&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;fragment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlsplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;链接中没有 #Base64 片段&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;fragment&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlsafe_b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;这个二维码没有个人 token，不能按本教程直接转换。&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; activationMethod=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;activationMethod&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;seed_hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;seed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;seed_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed_hex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b32encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ascii&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;passwordLength&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;timeStep&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BNBU:DKEY&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;issuer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BNBU&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;otpauth://totp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?secret=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;amp;issuer=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;algorithm=SHA1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;amp;digits=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;period=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;activationMethod: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;activationMethod&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;seed 字节数: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;digits: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;period: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;下面一行是登录凭据，不要发给任何人：&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在脚本所在目录运行：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="p"&gt;.\&lt;/span&gt;&lt;span class="n"&gt;convert_dkey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;py&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我这次实际读到的结构是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;activationMethod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;crypto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;seed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;40 位 Hex，已删除&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;timeStep&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;passwordLength&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;seed&lt;/code&gt; 的 40 位 Hex 对应 20 字节，也就是 160 bit。&lt;code&gt;crypto: 1&lt;/code&gt; 在这次部署中对应 HMAC-SHA1，验证码每 60 秒更新一次，共 6 位。教程里的数字来自这次 BNBU 实测，但脚本仍以你自己的 JSON 字段为准。&lt;/p&gt;&#10;&lt;h2 id="第五步先对码不要直接登录"&gt;&lt;a href="#%e7%ac%ac%e4%ba%94%e6%ad%a5%e5%85%88%e5%af%b9%e7%a0%81%e4%b8%8d%e8%a6%81%e7%9b%b4%e6%8e%a5%e7%99%bb%e5%bd%95" class="header-anchor"&gt;&lt;/a&gt;第五步：先对码，不要直接登录&#10;&lt;/h2&gt;&lt;p&gt;先把脚本生成的 URI 导入支持自定义周期的验证器，例如 Bitwarden。不要急着填进 OpenConnect。&lt;/p&gt;&#10;&lt;p&gt;同时打开宁盾官方 App 和新的验证器，观察同一个 60 秒周期内的六位码。至少连续跨过 2 到 3 个周期，每次都完全一致，才能确认：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Hex 解码正确。&lt;/li&gt;&#10;&lt;li&gt;Base32 转换正确。&lt;/li&gt;&#10;&lt;li&gt;算法是 SHA-1。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;digits=6&lt;/code&gt;。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;period=60&lt;/code&gt;。&lt;/li&gt;&#10;&lt;li&gt;电脑和手机时间同步。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;只对上一次不够。周期或时间刚好错位时，偶尔也可能造成误判。对码失败时，先检查自动时间与时区，再核对脚本读出的 &lt;code&gt;timeStep&lt;/code&gt;、&lt;code&gt;passwordLength&lt;/code&gt; 和 &lt;code&gt;crypto&lt;/code&gt;，不要连续尝试 VPN 登录。&lt;/p&gt;&#10;&lt;h2 id="第六步把-totp-与密码交给-openconnect-gui"&gt;&lt;a href="#%e7%ac%ac%e5%85%ad%e6%ad%a5%e6%8a%8a-totp-%e4%b8%8e%e5%af%86%e7%a0%81%e4%ba%a4%e7%bb%99-openconnect-gui" class="header-anchor"&gt;&lt;/a&gt;第六步：把 TOTP 与密码交给 OpenConnect-GUI&#10;&lt;/h2&gt;&lt;p&gt;对码通过后，编辑 OpenConnect-GUI 配置：&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="脱敏后的 OpenConnect-GUI 完整配置" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.huangqixuan.com/img/anyconnect/openconnect-profile-redacted.png"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Username 与 OTP URI 已脱敏，其余项目按实测配置保留。&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;需要改动的项目：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;项目&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;设置&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OTP Token&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;TOTP (RFC6238)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OTP 右侧输入框&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;粘贴完整 &lt;code&gt;otpauth://&lt;/code&gt; URI&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Batch mode&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;勾选&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Minimize on Connect&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;按需勾选&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;保存后连接。第一次可能仍要完成一次密码输入，让 GUI 保存静态密码。成功后断开，再重新打开 GUI 测试第二次连接。&lt;/p&gt;&#10;&lt;p&gt;最终验收标准不是“配置保存成功”，而是：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;点击 Connect 后不再要求输入静态密码。&lt;/li&gt;&#10;&lt;li&gt;不再弹出宁盾六位码输入框。&lt;/li&gt;&#10;&lt;li&gt;日志进入 Connected，学校内网资源可以正常访问。&lt;/li&gt;&#10;&lt;li&gt;断开后再次连接仍然成功。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="如果二维码里没有-token"&gt;&lt;a href="#%e5%a6%82%e6%9e%9c%e4%ba%8c%e7%bb%b4%e7%a0%81%e9%87%8c%e6%b2%a1%e6%9c%89-token" class="header-anchor"&gt;&lt;/a&gt;如果二维码里没有 token&#10;&lt;/h2&gt;&lt;p&gt;如果脚本提示 &lt;code&gt;activationMethod=2&lt;/code&gt; 或没有 &lt;code&gt;token&lt;/code&gt;，你拿到的是公共激活码。这种二维码只有宁盾服务器信息，不能凭空推导个人 seed。&lt;/p&gt;&#10;&lt;p&gt;宁盾转换的三篇参考文章还记录了两条备用路线：一种是在令牌绑定网页中检查本人 &lt;code&gt;refreshMobileToken&lt;/code&gt; 响应，另一种是在本人 App 激活时检查返回数据。响应里的 seed 可能是 Base64，也可能是 Hex，而且某些 &lt;code&gt;data&lt;/code&gt; 字段还是双重编码的 JSON。&lt;/p&gt;&#10;&lt;p&gt;这两条路线会接触账号、激活流量和个人二次认证密钥，环境差异也很大。BNBU 当前可以从门户重新获取包含 token 的个人二维码，因此本文不把抓包作为默认步骤。没有 token 时，优先回门户确认是否点了“重新获取手机令牌”，或者联系 ITSC 获取正式支持。&lt;/p&gt;&#10;&lt;h2 id="收尾把暴露面清干净"&gt;&lt;a href="#%e6%94%b6%e5%b0%be%e6%8a%8a%e6%9a%b4%e9%9c%b2%e9%9d%a2%e6%b8%85%e5%b9%b2%e5%87%80" class="header-anchor"&gt;&lt;/a&gt;收尾：把暴露面清干净&#10;&lt;/h2&gt;&lt;p&gt;完成后检查并删除这些内容：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;个人二维码截图。&lt;/li&gt;&#10;&lt;li&gt;保存完整二维码文本的文件。&lt;/li&gt;&#10;&lt;li&gt;解码后的 JSON。&lt;/li&gt;&#10;&lt;li&gt;包含 seed、Base32 或完整 URI 的终端历史、聊天记录和剪贴板。&lt;/li&gt;&#10;&lt;li&gt;为排查临时生成的日志与抓包文件。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;如果真实 seed 或 Base32 曾经发进聊天、上传网盘或交给在线转换网站，按已经泄露处理。回 Portal 重新获取手机令牌，让旧令牌失效，再用新的二维码重新做一次。不要只删除聊天记录后继续使用旧密钥。&lt;/p&gt;&#10;&lt;p&gt;还要接受一个现实：把静态密码和 TOTP seed 都放进同一台电脑后，便利性提高了，双因素原本的设备隔离却减弱了。Windows 登录、磁盘加密和电脑的物理安全会变得更重要。&lt;/p&gt;&#10;&lt;h2 id="回头看这次折腾"&gt;&lt;a href="#%e5%9b%9e%e5%a4%b4%e7%9c%8b%e8%bf%99%e6%ac%a1%e6%8a%98%e8%85%be" class="header-anchor"&gt;&lt;/a&gt;回头看这次折腾&#10;&lt;/h2&gt;&lt;p&gt;我最开始只是受不了每次连接都要输入账号密码，再拿手机、解锁宁盾令牌、抄六位码，也不喜欢 Cisco AnyConnect 的服务一直随系统启动。&lt;/p&gt;&#10;&lt;p&gt;中间试过 GUI、命令行脚本、密码自动填写、宁盾 Push、二维码识别，甚至差点走到手机抓包。真正解决问题的几个点都很具体：Batch mode 能保存密码；个人重新获取的二维码与公共二维码不同；Hex、Base32 和 &lt;code&gt;otpauth://&lt;/code&gt; 只是同一密钥的不同表示；OpenConnect 9.20 之后才支持完整 URI 与非默认 TOTP 周期。&lt;/p&gt;&#10;&lt;p&gt;现在我打开 OpenConnect-GUI，点击一次 Connect 就结束了。折腾过程比每天多输几次验证码花的时间多得多，但我终于不用再和那个重复流程较劲。&lt;/p&gt;&#10;&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://haotian22.top/546c6c89.html" target="_blank" rel="noopener"&#10; &gt;HaoTian：UIC AnyConnect 的校园 VPN 研究&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://baka.link/zh/posts/dkey-token-to-standard-totp/" target="_blank" rel="noopener"&#10; &gt;Kirin：将 Dkey Token（宁盾令牌）转换为标准 TOTP&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://blog.steven53.top/p/%E5%AE%81%E7%9B%BE%E4%BB%A4%E7%89%8Cdkey-token%E8%BD%AC%E6%8D%A2%E4%B8%BA%E6%A0%87%E5%87%86totp/" target="_blank" rel="noopener"&#10; &gt;Steven：宁盾令牌（Dkey Token）转换为标准 TOTP&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.jianyun.run/posts/convert-ningdun-token-to-standard-2fa-totp/" target="_blank" rel="noopener"&#10; &gt;唐长老：宁盾令牌转标准 2FA TOTP&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.infradead.org/openconnect/token.html" target="_blank" rel="noopener"&#10; &gt;OpenConnect 软件令牌说明&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.infradead.org/openconnect/changelog.html" target="_blank" rel="noopener"&#10; &gt;OpenConnect 更新记录&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://itsc.bnbu.edu.cn/BNBU%20Campus%20VPN%20Service%20Guideline--2025.pdf" target="_blank" rel="noopener"&#10; &gt;BNBU Campus VPN Service Guideline 2025&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://itsc.uic.edu.cn/ningdunzizhujiebangcaozuoshouce.pdf" target="_blank" rel="noopener"&#10; &gt;宁盾自助解绑操作手册&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;学校认证策略、门户页面和客户端版本以后都可能变化。重新操作时，以本人二维码里的字段、OpenConnect 实际版本和一次真实连接为准。&lt;/p&gt;&#10;</description></item></channel></rss>