搜「在线加密」或「浏览器加密文件」,结果页几乎都会写:计算在本地、文件不上传、密钥不离开设备。这些句子读起来安心,却没法当作证据。页面可以这么写,脚本仍然可以把输入框里的口令、文件切片或地址栏里的密钥用 fetch 发出去。你要核对的不是文案,而是这一次操作有没有把明文送出浏览器。

本文不讲「怎么选一个加密产品」,也不复述某个工具页的功能清单。问题只有一个:宣称在浏览器里加密时,你能用开发者工具和地址栏当场看见什么。MyPassGen 的文件加密与阅后即焚都走 Web Crypto API 的 AES-256-GCM,打开即可使用;下面用同一套核对方法说明边界,而不是让你相信一句「我们不保存」。

口号无法核对,流量可以

「客户端加密」这个词被用滥了。有的站点把文件 POST 到自己的服务器,再在服务端用 AES 包一层,回一个下载链接——对用户来说也叫「在线加密」。有的站点在标签页里调用 crypto.subtle.encrypt,下载 .lock 或 .enc,全程没有业务上传。两种流程在营销页上可以写成同一句话,在 Network 里完全不像。

浏览器不会替你审查文案。它只会把这次点击触发的文档请求、脚本、图片、XHR / Fetch 列出来。请求行里有没有明文,请求体里有没有口令,分析上报的 url 里有没有 # 后面的密钥——这些都能看见。看不见的才是「默认不离开本机」;看见了,口号就可以作废。

核对不需要读完源码。你需要的是一次可重复的操作:打开面板、做一件具体的事(生成密码、检测一段口令、加密一个小文件、创建一条焚链),然后逐条点开请求。下面先分清「本地」到底指哪一层,再对照井号与问号,最后落到步骤。

「本地」指哪一层

Web Crypto API 是浏览器提供的密码学原语接口,通过 crypto.subtle 做哈希、派生、加密和解密。W3C 在用例里写过一种合法架构:用户在浏览器里选密钥、加密文档,再把加密后的数据上传到服务商。也就是说,API 保证的是「加密运算可以在本机发生」,并不禁止之后把密文发出去,更不保证页面作者不会先上传再加密。

Web Crypto 还要求安全上下文:正式环境里通常是 HTTPS(本机 localhost 例外)。这不是营销点,而是接口能不能用的前提。页面若在普通 HTTP 上声称「使用了 Web Crypto」,先看地址栏是不是锁,再谈算法。

AES-256-GCM 在本机意味着什么

MyPassGen 初版只使用 AES-256-GCM。按 MDN AesGcmParams 与其所引的 NIST SP 800-38D,GCM 是认证加密:密文被改过,解密会失败,而不是解出一段乱码还让你当正文用。初始化向量(IV)建议 96 位,也就是 12 字节,且同一密钥下每次加密必须换新 IV;IV 本身不必保密,可以和密文放在一起。认证标签默认 128 位。这些数字可以在实现里核对,不需要当成口号背诵。

文件加密还会先用口令派生密钥。本站文件盒的派生是 PBKDF2,100,000 次迭代,哈希为 SHA-256,盐 16 字节,再得到 256 位 AES-GCM 密钥;单文件上限 5 GB,输出 .lock 或 .enc。口令若被一起 POST 出去,派生次数写得再高也救不了——所以核对的第一对象仍是流量,不是迭代次数。

三种完全不同的「在线加密」

把流程摊开,至少能分成三类。第一类:浏览器只负责选文件,字节送进服务器,加密在对面完成。第二类:浏览器先加密,再只上传密文,密钥走另一条通道(例如 URL 的 #)。第三类:加密结果直接下载到本机,业务请求里根本没有文件体。三类都可以叫「网页上的加密」,只有后两类有资格谈「明文默认不上传」。你要做的,是用 Network 判断眼前这个页属于哪一类。

先定核对对象,再点开始

生成密码、检测口令、清洗链接、加密文件,期望是「没有把明文当业务数据发出」。阅后即焚期望不同:允许看到密文字段,但不该看到原文,也不该在请求行里看到 # 后面的密钥。把两类期望混在一起,会把正常的密文 POST 误判成「泄漏了」。

井号与问号不是一类东西

网址可以拆成方案、主机、路径、查询(? 后面)和片段(# 后面)。RFC 3986 §3.5 把 # 后面称为 fragment:它标识的是取回主资源之后,在客户端解释的次级位置。MDN 对 URI fragment 的说明更直白:请求该 URI 时,fragment 不会发给服务器,由客户端在资源到达后再处理。

查询字符串正好相反。打开 https 链接时,? 后面的 名字=值 会进入请求行,也会进对端访问日志。上一篇外发链接时哪些参数能删已经写过:utm_source 和 id= 都活在 query 里。密钥如果写成 ?key=,服务器、反向代理和 CDN 日志都能看见;写成 # 后面,默认请求行里没有这一段。

Referer 也剥掉 fragment。MDN Referer 写明:该头可以带源、路径和查询,不可以带 URL fragment 和用户名密码。W3C Referrer Policy 在「发送前剥离」步骤里同样把 fragment 置空。因此「密钥放在 # 后面,就不会随 HTTP 请求发给托管密文的那台机器」是协议行为,不是某家站点的口头保证。

阅后即焚的链接形态是 s.html?id={id}#{key}:query 里只有密文编号,密钥只在井号后。创建时,浏览器先用 32 字节随机密钥做 AES-256-GCM(IV 12 字节),再把密文 POST 出去;阅读时,脚本从 location.hash 取密钥,在本机解密。明文上限 32 KB。你要核对的是:创建请求的 JSON 里应有密文字段,不应有原文;阅读页文档请求的 URL 里应有 id=,不应有 # 后面那一截。

用 Network 当场核对

Chrome、Edge、Firefox、Safari 都提供网络面板。名称略有差别,步骤一样。不必安装插件,也不必把文件交给第三方「检测站」——把完整 URL 或文件再上传一次,反而扩大了暴露面。

  1. 打开开发者工具,切到 Network(网络)。勾选「保留日志 / Preserve log」,避免跳转后列表被清空。
  2. 过滤选 Fetch / XHR(或「XHR」)。静态资源可以后看;业务上传几乎总走这类请求。
  3. 做一次你要核对的操作:生成密码、输入一段可丢弃的测试口令、粘贴一条带 UTM 的链接、选一个小文件加密,或写入一句测试机密再创建焚链。
  4. 逐条点开新出现的请求。看 Request URL:文档和 API 地址里不应出现你刚输入的明文,也不应出现 # 及后面的密钥。
  5. 打开 Payload / Request 面板。JSON 或表单里不应出现原文、口令、待测密码或完整文件字节。阅后即焚允许出现密文字段,它应像随机二进制的 Base64,而不是你刚敲进去的那句话。
  6. 再扫一遍分析或统计请求(常见名字含 matomo、collect、g/collect)。看上报的页面地址或自定义参数里,有没有把 location.href 连同井号一起送出。

文件加密还有一个更硬的对照:断开网络或打开飞行模式,再加密一个小文件。若计算声称完全在本机,下载 .lock / .enc 仍应成功。阅后即焚做不到这一步——它必须把密文交给服务器暂存,断网时创建失败是预期,不是打脸。把「完全无请求」当成唯一及格线,会误伤零知识焚链。

密码检测页可能去拉一份静态弱口令名单(例如本站的 leaked-top10k.txt)。那是词表,不是你的输入。核对时看请求:允许看到词表文件,不允许在 query 或 body 里看到输入框里的待测密码。这和全网 HIBP 撞库不是一回事;后者会把哈希或密码片段发到外部接口。

你在做什么 Network 里可以出现 不该出现
生成随机密码 页面自身的脚本与样式 生成结果、勾选的字符集被 POST
本机检测口令 静态弱口令名单 输入框里的待测密码
清洗链接或脱敏 无原文业务请求 完整 URL、证件号、邮箱原文
阅后即焚 密文、id、过期与阅读次数 明文;请求行里的 # 密钥
加密文件 无文件体业务上传 文件字节、口令

MyPassGen 按这张表工作:密码长度随机模式 6–128 位(默认 16,低于 8 位会提示较弱);检测对照本地名单,不是全网撞库;清洗与文件加解密默认不把原文当业务数据上传;焚链只暂存密文。全部打开即用,没有注册门。表里的「不该出现」你自己就能在面板里打勾,不必先接受品牌承诺。

密文出站不等于明文出站

零知识分享和「完全单机」容易被揉成一句。文件盒属于后者:结果落到下载目录,服务器没有这份文件的业务副本。焚链属于前者:对面必须能取出一段密文,接收方才能在自己的浏览器里解密。服务端看见密文、看不见密钥,这是设计,不是事故。

判断泄漏的标准因此要拆开。创建焚链时,POST 体里出现很长的 Base64 是正常的;若同一字段里能读出你刚输入的「测试用 API Key」,才是明文出站。阅读页的文档请求应是 s.html?id=…,Network 列出的 Request URL 在规范下不会带 #。点开该请求的 Headers,确认请求行和 Referer 都没有密钥。

GCM 的认证标签让「改几个字节再给你解」变得困难:标签对不上,decrypt 直接失败。这保护的是完整性和真实性,不是「链接绝对不会被转发」。谁拿到完整 URL(含井号后的密钥),谁就能在阅读次数用尽之前解密。Network 核的是服务器有没有密钥;聊天记录、邮件和截图是另一条风险,下一节单独说。

不要把完整焚链贴进会上传原文的「检测站」

井号后面的密钥对 HTTP 服务器不可见,对你粘贴的下一个网站完全可见。要核对应在自己的开发者工具里看请求,而不是把 s.html?id=…#… 整段交给来路不明的扫描页。

规范管不到的泄漏面

HTTP 不发送 fragment,并不等于密钥永远不离开这台电脑。页面脚本可以读 window.location.hash,再自己 fetch 出去——这是实现错误,不是协议漏洞。所以第六步要看分析请求:若统计把 location.href 原样当页面地址上报,井号后面会进统计日志。正确做法是上报路径或丢弃 hash,而不是依赖「反正 HTTP 不会带」。

浏览器历史、同步的标签和部分崩溃报告会保存完整 URL。把焚链留在地址栏,等于把密钥留在本地历史里。分享时用一次性通道,读完不要把带 # 的地址写进工单模板。Referer 虽然剥 fragment,查询参数仍可能被带去第三方;密钥绝不能改放进 ?key=。

屏幕、剪贴板和群聊日志在协议之外。同事把完整链接截进聊天,服务器依然没有密钥,接收方之外的人却有了。本地加密回答的是「计算发生在哪、默认谁看不见明文」,不回答「拿到完整链接的人会不会转发」。文件口令也一样:.lock 可以进网盘,口令必须走另一条渠道;短机密更适合焚链,而不是把口令写进同一封邮件正文。

常见误区

「断网不能用,所以一定在上传明文。」焚链必须暂存密文,断网失败只说明它依赖一次密文传输。要看的是那一次传输的内容,不是有没有请求。

「HTTPS 已经加密,再谈本地加密是重复。」HTTPS 保护的是传输路径上的窃听者。服务器作为 TLS 终点,仍能看到请求体和 query。本地加密要挡住的是「服务端或中间分析默认读到明文」这一层,和 TLS 叠在一起,职责不同。

「页面写了 Web Crypto,就等于明文不上传。」Web Crypto 只提供原语。先 encrypt 再上传密文,和先 FormData 再在服务器加密,都可以调用同一个 API 名字做点缀。面板里的 payload 比页脚标语优先。

「看不到业务域名的请求,就绝对安全。」扩展、系统代理和部分企业网关不会全部画在网页 Network 里。面板能证伪「完全无上传」的广告,不能证明世界上没有第二条通道。对日常选用在线工具,证伪已经够用:看见明文,立刻停手。

从哪一步开始

先选一件今天就要做的、可丢弃的操作。加密文件最容易对照:选一张无敏感信息的截图,输入临时口令,打开 Network,点加密,确认没有文件体 POST,再下载 .lock 或 .enc。需要把同一张图发给别人时,密文走网盘,口令走另一条消息。单文件不要超过 5 GB。

若你要传的是一句话密码或恢复码,改用阅后即焚:写入测试句,生成链接,检查创建请求只有密文;用隐私窗口打开阅读页,看文档请求行是否只有 id=。不要把阅读页当成可收录的内容页去传播——它是一次性密文场景。链接清洗则继续按上一篇的对照表删 UTM 和点击 ID,同样在本机完成。

做完一次,你就已经能回答本文的问题:本地加密是不是真的,不看口号,看这一次流量里有没有明文、口令和井号后的密钥。算法用 AES-256-GCM、计算走 Web Crypto、工具打开即用,都是可以并列核对的约束,而不是需要先注册才能听见的承诺。

常见问题

为什么文件加密可以断网,阅后即焚不行?

文件加密的结果是本机下载;阅后即焚必须让接收方稍后取到密文,所以创建时会 POST 密文。两者都可以在浏览器里完成 AES-256-GCM,出站内容的资格不同:前者不应有文件体,后者允许密文、不允许明文和密钥。

井号后面的密钥,服务器真的收不到吗?

按 URI 与 HTTP 的常规实现,文档请求和 Referer 都不带 fragment。这可以用 Network 里那一条文档请求当场看。脚本若主动读取 location.hash 并上报,那是页面自己的行为,也要在 Fetch 列表里查。

把密钥放进查询参数是不是更方便?

方便,而且会进服务器日志。?id= 定位密文可以;?key= 等于把解密能力交给托管方和沿途日志。需要接收方能解密、托管方不能解密时,密钥放 # 后面。

核对这些需要注册吗?原文会不会被分析脚本收走?

不需要注册。分析若只记页面类型或去 hash 后的路径,不会带走输入框内容;若把完整 href 上报,井号后的密钥可能进统计。打开 Network 看分析请求的 query,比阅读「我们重视隐私」更直接。