换工作、开新站、把旧口令改成「再加一个感叹号」之前,很多人会先搜一遍:这个密码有没有泄露。搜索结果里,「密码强度检测」「Have I Been Pwned」「在线泄露查询」常常排在一起,标题都像在回答同一句。打开之后,有的页把输入框内容 POST 到自己的服务器,有的只向第三方接口要一份哈希后缀列表,有的只下载一份公开弱口令词表,在标签页里比对。三种流程对「有没有把密码送出去」的答案完全不同。

本文不复述某个检测页能打出弱、中、强,也不教你去撞别人的库。问题只有一个:本机对照一份公开名单,和把密码交给全网查询,分别能证明什么、不能证明什么。MyPassGen 的密码检测属于前者:待测密码不上传,对照的是随页加载的公开弱口令名单,打开即可使用。下面把边界摊开,并用开发者工具当场核对。

三种完全不同的「查密码」

先把名字对齐。否则「本地」「不上传」「查过泄露库」可以同时出现在营销句里,却指向三套协议。

做法 离开浏览器的是什么 能回答的问题
把口令原文交给网站 明文,或可还原的表单字段 对方服务器说「查过了」——你看不见依据
k-匿名范围查询(如 HIBP) SHA-1 或 NTLM 哈希的前 5 个十六进制字符 这个哈希有没有出现在对方维护的全库里
本机对照公开弱口令名单 一份静态词表文件,不是你的输入 是不是「123456」「password」这类已被反复使用的口令

第一类最省事,也最难核对。页面可以写「查询后立即删除」,日志、备份和分析脚本仍可能留下副本。第二类是 Have I Been Pwned Pwned Passwords 公开说明的做法:浏览器先算哈希,只把前缀发给 api.pwnedpasswords.com/range/{prefix},后缀列表拉回来再在本机比对。第三类连前缀都不发。词表是公开的常见弱口令,体量通常是几百到一万条,不是「全网每一次拖库」。

美国国家标准与技术研究院在 NIST SP 800-63B-4 里要求验证方在设立或更换口令时,对照一份「常用、可预期或已泄露」的黑名单,并且不要再叠加强制大小写、数字、符号组合规则。它同时写明:黑名单的主要用途是挡住在线猜测里最容易先试的那些;名单大到超过限流窗口,增量收益很小。这正好解释了本机 Top 名单的位置:它是拦截常见弱口令的第一道筛,不是全网档案检索。

先分清问题,再选工具

「会不会被字典先猜中」和「有没有出现在某次具体拖库」不是同一句。本机名单回答前一句;全库范围查询回答后一句里「对方这份库收不收得到」。两者都不能代替:不要在多个站点复用同一条口令。

把原文交给网站意味着什么

仍有一批「在线密码检测」把输入框里的字符串直接放进请求体。有的写成 JSON 字段 password,有的放进查询字符串,有的先做一层可逆编码再 POST。对浏览器来说,这和登录表单没有本质差别:对端主机、反向代理和访问日志都能看见这一次提交。

HTTPS 只保护传输路径上的窃听者。它不禁止对端把明文写入磁盘,也不禁止分析脚本把字段再转发出去。你要减少的是这一次检测有没有多一个知情者,不是改写 HTTPS 的职责。上一篇浏览器里做加密怎么当场核对已经写过同一条原则:口号无法证明,流量可以。

还有一种更隐蔽的交出去法:把待测密码写进地址栏的查询参数,再把链接转发给同事或丢进工单。查询字符串会进入请求行,也会进对端日志;部分第三方资源的 Referer 仍可能带上路径和 query。检测用的口令如果是真实账户密码,这一步等于主动扩散。正确默认值是:真实口令只留在当前标签页的输入框里,不要写进 URL,也不要贴进会上传原文的「检测站」。

只发哈希前缀的全库查询

Have I Been Pwned 的范围接口把「全库」和「不交原文」拆开。官方文档写的步骤是:在本地把口令编成 UTF-8,算 SHA-1(也可选 NTLM),取出哈希的前 5 个十六进制字符,请求 GET https://api.pwnedpasswords.com/range/{前五位}。响应是一批后缀加出现次数,用冒号分隔;客户端在本机把前缀和后缀拼回去,看有没有整段命中。Troy Hunt 在 Understanding Have I Been Pwned's Use of SHA-1 and k-Anonymity 里把这种模型称为 k-匿名:服务器看见的是一个哈希桶,不是完整哈希,更不是口令原文。

Cloudflare 与 HIBP 合作时公开过一组可核对的数字:前缀取 5 位时,同一桶里哈希数量的中位数大约是 305,响应体积中位数大约 12.2 KB。官方还提供 Add-Padding: true:把每条响应填充到约 800–1000 行,降低「看响应有多长就能猜你查的是哪一类常见口令」的旁路。这些都说明:范围查询比把原文 POST 出去克制得多,但它仍然是一次出站请求。Network 里会出现对 api.pwnedpasswords.com 的访问;断开网络,这类查询应当失败。

范围查询也不是「绝对匿名」。前缀会告诉观察者你的哈希落在哪一个 165 分桶里;页面脚本如果在发前缀之前就把原文另存一份,k-匿名帮不上忙。邮箱检索和口令检索还不是同一条 API:按 HIBP v3,邮箱的哈希范围查询用的是前 6 位,而且那是另一套产品能力。本文只对照「查这条密码有没有出现在口令库」,不把邮箱订阅、域名监控写成同一件事。

因此,把「用了 HIBP」写成「密码完全没离开设备」不准确。准确说法是:原文和完整哈希按设计不发给对方;前缀和一次范围请求会离开。你若接受「让一家维护全库的第三方知道哈希桶」,这是合理的工程折中。你若要求「这一次检测连前缀都不出站」,就应走下一节的本机名单,并承认覆盖面更窄。

本机名单只拉词表,不交出口令

本机对照的流程反过来:先把一份公开弱口令列表下载到当前源,再在脚本里做集合查找。Network 里允许出现词表文件,不允许出现输入框里的待测密码。词表本身是公开数据,常见来源包括 OWASP / Daniel Miessler 维护的 SecLists 里「一千万口令截取 Top N」那一类文件。它回答的是「攻击者字典会不会先试到你」,不是「某一次未公开的拖库里有没有你」。

MyPassGen 按这个边界做检测。页面打开后会请求 data/leaked-top10k.txt:当前约 860 行,每行一条小写口令,开头是 123456、password、qwerty 这类反复出现的弱口令。比对在浏览器里完成:先转小写,再生成有限变体——把 @ / 4 看成 a、0 看成 o 等常见 leet,以及去掉末尾一到三位数字——再看是否落在集合里。命中即标为弱,哪怕看起来「很长」。词表加载失败时,脚本退回到内置的最小集(password、123456 等),而不是改去请求外部泄露接口。

强度评估是另一条本地计算。字符池按是否含小写、大写、数字、符号累加(26 + 26 + 10 + 32),熵近似为长度乘以 log2(字符池),重复字符过多会再打折。分级阈值是:约 40 bits 以下为弱,60 以下为中,80 以下为强,以上为极强。长度不足 8 位会单独提示较弱;随机生成器允许 6–128 位,默认 16 位。离线暴力破解按每秒 1010 次猜测估算,在线限流按每秒 103 次——这是给自己看的数量级,不是对某块 GPU 的承诺。分析事件只记等级和是否命中名单,不把口令原文送进统计。

这些数字你可以在本机对上:词表行数用编辑器或 wc -l 就能数;Network 里应看到词表,不应看到输入。它仍然不能写成「查过全网」。860 条连 SecLists 公开的 Top 10,000 都没有覆盖完,更不要说 HIBP 那种按哈希收录的完整口令库。未命中的唯一诚实结论是:它不是这份名单里的常见弱口令。

未命中名单,不要写成「安全」或「从未泄露」

本机名单拦的是字典里最前面那一批。你的口令完全可能出现在名单之外的拖库、撞库组合或针对你站点的社工猜测里。需要换新密码时,在本机生成一条,不要在原词后面加年份或感叹号继续复用。

强度条证明不了没泄露

强度条估算的是穷举空间,不是档案检索。一条 20 位随机口令的熵可以很高,一旦它已经出现在别人的泄露文件里,攻击者不会按字符集从头猜,会直接拿现成列表去撞。反过来,Password1! 往往能骗过「必须有大小写、数字、符号」的表单,却几乎总会落进公开弱口令名单。NIST 之所以不再要求组合规则,依据就是这类绕法太常见。

所以检测页若同时给出强度和名单结果,应以名单为准覆盖强度:命中即弱。强度条仍有用,它提醒你「太短、只有一类字符、含 qwerty 键盘序列」。它不能单独签发「可以继续用」的证明。你要的决策通常是两句:命中名单或短于 8 位——立刻换;未命中且足够长——仍然不要跨站复用,重要账户优先用口令管理器或本机生成器另写一条。

「全网查询显示没中」同样不是终点。范围查询依赖对方库的收录范围与更新节奏;库外的未公开泄露、内部系统口令、尚未被哈希收录的变体,都不会出现。它比本机 860 条宽得多,也仍是一份他人维护的样本。两种工具叠在一起用可以,前提是你分得清各自的出站内容和失败含义。

当场核对:词表可以有,口令不能有

下面这组步骤不依赖品牌承诺。用一条可丢弃的测试口令,例如 password 或你从未在任何账户用过的临时串。不要把正在使用的真实密码贴进演示。

  1. 打开开发者工具的 Network,勾选「保留日志 / Preserve log」。过滤先选 Fetch / XHR,再扫一遍文档与其他请求。
  2. 刷新检测页。应看到一份静态名单(本站是 leaked-top10k.txt)。点开该请求:响应是一行一条口令,请求体应为空。
  3. 在输入框键入测试口令,等结果出现。再看新增加的请求:URL、query、JSON、表单字段里都不应出现刚才键入的那串字符。
  4. 搜索面板里的 pwnedpasswords、hibp、range/。本机名单方案不应出现这些主机;若出现,说明页面走了范围查询,应改用上一节的标准理解它。
  5. 再扫分析请求(常见路径含 matomo、collect)。事件名可以有「弱 / 中 / 强」,值里不应有口令原文。
  6. 需要对照「全库方案」时,用同一条可丢弃口令在会调用 HIBP 的页面测一次:应看到对 api.pwnedpasswords.com/range/ 的 GET,路径只有 5 个十六进制字符,没有原文。

断开网络再输入一次,是本机方案的加分核对:词表若已缓存,强度和命中仍应能算;范围查询应失败。不要把「完全无请求」当成唯一及格线——第一次打开必须把词表拉下来,这和把口令 POST 出去不是一类流量。上一篇讲本地加密时用过同一套面板;这里只多盯一件事:业务请求里有没有你的输入。

误区:绿条、全网、以及「查过就安全」

「强度条是绿的就没泄露」不成立。绿条描述的是字符空间,泄露描述的是别人是否已经拿着同一条字符串。两件事可以同时为真。

「写了本地计算就等于没出站」也不成立。本机名单会下载词表;范围查询会下载某个哈希桶。你要禁止的是原文和完整哈希当业务数据离开,不是禁止一切 HTTP。

「没出现在 HIBP 就可以在所有网站复用」是最贵的误区。凭证填充攻击用的是「一条口令打许多站」,不要求这条口令先出现在公开榜单前一千名。本机名单和全库查询都解决不了复用。新密码应在本机生成,长度按随机模式用够默认的 16 位或更长,再分别写入各站。

「把密码贴进聊天让同事帮看强不强」把检测变成了传播。需要把一条新口令交给别人时,一次性短秘密走阅后即焚,密钥放在 URL 的 # 后面;需要可反复解密的文件走文件加密盒。检测页解决的是「自己先看名单和强度」,不是密钥交换。

从哪一步开始核对

先测一条你准备淘汰、或从未用于真实账户的口令。打开检测页,看 Network 里的词表请求,输入后再确认没有把该串送出。命中名单,就不要再改两个字符继续用;到密码生成器本机生成一条,长度 16 或以上。未命中,只说明它不是这份公开弱口令集合里的成员,仍按站点分开保存。

若你的问题其实是「这条密码有没有出现在某次已知的大规模拖库」,本机八百余条不够用。应使用会走 k-匿名范围查询、并且你能在 Network 里看到「只发了 5 位前缀」的工具,而不是把原文交给来路不明的查询页。两种核对都做完,仍然记得:打开即用的检测不能代替各站自己的登录保护,也不能证明服务端以后不会再泄露。

做完这一条,你就已经能回答标题里的差别:本机对照泄露名单,证明的是「像不像攻击者先试的那一批」;全网密码查询——若实现正确——证明的是「在对方这份哈希库里有没有整段命中」。前者词表出站、口令不出站;后者前缀出站、原文不出站。把原文 POST 出去的第三种,不属于这两种。

常见问题

本机没命中,还要不要去 Have I Been Pwned 查?

取决于你要哪一句答案。没命中只说明它不是这份公开弱口令名单里的成员。若你还想知道它有没有出现在 HIBP 收录的口令哈希库,需要另做一次范围查询,并在 Network 确认只发了 5 位前缀。两件事可以分开做,不要混成「已经查过全网」。

范围查询会不会把我的密码告诉 HIBP?

按官方设计和 Troy Hunt 的说明,对方收到的是 SHA-1 或 NTLM 哈希前 5 位,不是口令原文,也不是完整哈希。匹配在你的浏览器里完成。它仍是一次出站请求;不接受任何前缀离开设备时,就只用本机名单,并接受覆盖面更窄。

检测密码需要注册吗?待测密码会不会上传?

不需要注册。本机方案下,待测密码应留在输入框;离开浏览器的应是词表文件,以及不含原文的分析事件。是否做到,以 Network 为准,不以页面上的「不上传」为准。

强度里的「离线要破解 N 年」能信吗?

那是按固定猜测速率做的数量级提示,不是对某块显卡或某个词表攻击的测定。名单命中时,攻击者不会按这个时钟穷举。把它当成「太短 / 字符类太少」的参考,不要当成安全保证。