终端共享工具有一堆(ttyd、TermPair、sshx 之类的)。但有个东西讲的人少,我自己绕了一会儿才咔哒一下扣明白:既然数据全要走中继服务器,它怎么做到端到端加密的?密钥到底怎么给到浏览器,还能不让中继看见?
先别急,我们从底层画起。
所有这类工具,底层都在转同一个玩意儿:伪终端(PTY)。内核开一对文件描述符,主端给终端模拟器,从端给 shell。工具干的事,就是在主端和你的浏览器之间当搬运工。
浏览器 <──数据流──► 工具(搬运工) <──PTY主端──► shell(PTY从端)
ttyd 就是纯搬运工。C 写的单进程,前端直接以 C 字节数组塞进二进制文件。一个可执行文件搞定,但它默认流量明文,假设服务器可信,默认连敲命令都不让。适合家里内网玩。
那 TermPair 和 sshx 呢?它们把中继服务器设计成了"瞎子"——只转发密文,结构上根本解不了密。
等等等等,先别急。问题来了:既然中继是瞎的,那解密密钥怎么给浏览器?
你肯定想过放 URL 参数里。对,也不对。它们确实用 URL,但用的是 # 后面的东西(fragment)。
https://中继服务器/share/abc123#这里是加密密钥
这是整个设计里最漂亮的一脚:浏览器发 HTTP 请求时,# 后面的东西根本不会被发到服务器。
就这一条。中继服务器只看到了 abc123 这个会话 ID,从头到尾碰不到密钥。浏览器拿到完整 URL,自己把 # 后面的密钥抠出来用。
这个东西不用打比方,URL fragment 那套机制画出来本身就够酷了。
密钥拿到了,接下来它俩的玩法开始分叉。TermPair 用 AES-128-GCM。把终端输出、浏览器输入、握手分开,各自用独立的密钥和计数器。为什么要分开?防随机数重用。撞车了加密就废了,分开各算各的。
sshx 走了另一条路。为了把链接弄短点,它的密钥只有 83 位熵。按理说不够安全,所以它硬生生套了一层 Argon2id,每个 guess 都得吃 19MB 内存。你拿 GPU 硬扛可以,算到死。
还有个很狠的设计。sshx 用 AES-CTR,按流号和偏移量定位加密。断线重连的时候,浏览器告诉服务器"我收到序号 1024 了",服务器从 1024 往后补发就行,接着解。不用从头重传。
它甚至抄了 Mosh 的预测性回显:你一敲键盘,客户端先在本地把字给你显示出来,等服务器的真实输出到了再纠正。网络再卡,你手下的字也是秒出。当然,遇到密码提示这种场景,它就不预测了。
扯远了。这些工程细节先不画,我最想说的还是那个 # 号。
原来"服务器不需要知道密钥也能转发密文",而且只需要一个 URL fragment 就能把这事儿办漂亮。安全不一定要靠把东西藏起来,有时候只要让该瞎的人瞎得彻底就行。这玩意儿真的太酷了。
