诸位老友,晚上好。这里是躺在草地上的 Chlorine。
久违的「风月」,意味着本文又是大量奇怪碎碎念的混合。如果老友们在前面看到了逻辑不严密或者说得不完整的地方,不妨先继续往后看一看,说不定后面小氯就把这个讲了哦——当然,也有可能确实是逻辑不严密或者说得不完整。
无地王
我们依然从老生常谈的话题开始吧。所谓的「数字根身份」。
好啦,好啦,小氯知道这个问题一股技术宅的酸腐气。但看在小氯今天新做的海盐蓝莓蛋糕的份上,继续听听好嘛?
对于 VTuber 们,根身份可能是 YouTube 账户;对于 X 上的意见领袖们,根身份可能是 X 账户;对于资深成熟的开源开发者们,根身份可能是 GitHub 账户;对于 IndieWeb 的成员们,根身份自然就是域名了。其实域名作为根身份已经相当好且自主了,但它毕竟还是 ICANN 的地盘。所以,我们会有一个不受中心机构管辖的、自主可控的身份嘛?
唔,Web3 嘛?小氯不喜欢那个哦。
画一个列联表的话,Google、GitHub 之类的 big tech 算是极度不可信——好吧,它们也不一定可靠,考虑到某邪恶五足生物的 uptime 已经掉到一个 9 都没有了(9% 也是 9)。Codeberg 之类呢?它们还算可信,但可靠性得打个问号——这相当于把你的身家性命交给了一群志愿者的爱心。ICANN,DNS 和域名系统呢?它们还算可靠,但可信性就难说了。
那——好吧,大家都知道这里会是什么了——密码学身份呢?
请期待小氯会说「它既可信又可靠」的老友自觉少吃一块蛋糕。它当然可以是既可信又可靠的,但更准确的说法是:它会和你保持一致。不论你是会把根私钥直接丢到百度网盘里,用 Shamir 秘密共享分送给自己信任的家人朋友,还是将它刻在钢板上埋进家里地下室的保险柜。
不管怎样,被自己的愚蠢气哭(误)总比被平台封禁感觉上稍微好一些。所以这大概就是目前的答案了,这也是当年密码朋克的拥趸们的答案。
至于用什么,大家自然会想到 GPG——准确说是 OpenPGP。有完整的密钥标准,有身份标识,有 WoT(信任网)管理社会关系,无处不在。不过好处说完了,坏处呢?
坏处就是除此之外。小氯闲着没事玩 Sequoia PGP 的时候写过一篇小文章讲这件事,感兴趣的老友可以去看一下。另外,小氯对 Sequoia PGP 的印象也一般,感觉用起来没有比 GPG 强太多。
于是直到现在。虽然说小氯有一套不错的 OpenPGP 密钥,它也并没有怎样惹动小氯,但……就是哪里不对劲对吧。
所以这和你这节的标题有什么关系啊喂?!
唔嗯,其实是说,OpenPGP 引以为傲(也是漏洞百出)的 WoT 和 key server 现在几乎没人用了,大家发现密钥几乎都是基于域名系统的 WKD 了。看起来域名才更像是根身份,而密钥是附属物品。
钥匙环
OpenPGP 烂成这样,自然会有人想着 let it rot——呃,这话好像也在之前的那篇文章里说过了。总而言之,就是 age、minisign 和老当益壮的 SSH 等一众豪杰。
但有点令人头痛的是:由于 OpenPGP 试图包揽身份层的努力虽然谈不上完美无缺吧,至少也可以说是一塌糊涂,age 之类的工具直接把身份层砍掉了。至于解决方案,好像要么是带外方案(包括其他身份方式),要么直接不管——好像这也在之前的那篇文章里说过了。好吧我承认了,这篇文章就是它的续集!但将军说过,密码学身份一定要是密码学的身份。所以我们暂时还不能顺着业界共识来。
说起来,小氯忘记在哪里听过一句犀利的话了:
我们不能为了让一群 Unix 呆子颅内高潮,就忍受糟糕的加密。
这句话的来源小氯找不到了,但对其内容小氯稍有点怀疑。
GPG 是 Unix——准确来说是它的徒子徒孙的原生工具之一吗?是。那 GPG——乃至整个 OpenPGP 协议,符合 Unix 哲学吗?
这个问题小氯持保留态度。从实证层面,GPG 大概是「Do one and well」的反义——「Do many and poorly」;它自己的软件架构也是典型的 super monolith;至于管道和互操作性……这点咱还是不评价了,您说对吧,GnuPG Made Hard。所以如果「Unix 呆子」说的是 Unix 哲学的拥趸,那面对 GPG,他们感到开心……这似乎不符合普遍理性下的智慧生命情绪变化预期。
所以为了让 Unix nerds 们真的手舞足蹈,我们得设计一套新的系统。
小氯的大概思路是:OpenPGP 的主子密钥架构是相当 inspiring 的,我们不妨借鉴下。SSH 和 minisign 都可以用来签名,那让它去签名一把其他密钥的公钥,不就相当于根密钥的 Certify 了嘛。至于其他的职责,分给 SSH 和 age 等一众类型来掌管,彼此不隶属。如果需要更替密钥,重新用根密钥签一个就行。虽然听起来不太结构化,但语义是正确的。至于密钥发现,用类似 WKD 的方法就好。反正 WoT 的正统 OpenPGP 都基本不用它了,WKD 还能顺带着绑定一下你的域名。毕竟,没有 OpenPGP 原本预想的线下身份强绑定,大家信任的并不是一串字符,说到底还是你的文章、代码和言行嘛。
听起来很美好,但小氯卡在了一个地方。按照小氯的想法,子密钥日常使用应该在 HSM(硬件安全模块,比如 YubiKey)里面,age 自然也包括在内。age 也有一个社区插件 age-plugin-yubikey。目前为止一切正常。
但问题是……这个插件不支持外部导入。
对小氯来说,不支持导入是很头痛的,因为这意味着没办法备份——自然也没办法安全地离线备份。age 社区的一个方法是用多接收者(多把公钥加密),这样做……也行吧,如果你有多把硬件钥匙。但小氯只有两把,而且几乎是同时买到的,估计也会在几乎同时过时。如果你把备用的 age 放在离线环境下,这看起来很好,但如果你的主要 age 密钥废掉了(比如 HSM 坏了),解密并重加密你的所有文件……我的天啊,简直是噩梦。
小氯就这样在这里卡了许久。直到我看到了这个 issue 下的这条回复……
简单来说,用 OpenSSL 手动生成私钥,然后写一个大概这样的配置文件 age.cnf:
[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3_age-plugin-yubikey
prompt = no
[ req_distinguished_name ]
0.organizationName = age-plugin-yubikey
organizationalUnitName = 0.5.0
commonName = Chlorine-chan # 可以自定义
[ v3_age-plugin-yubikey ]
subjectKeyIdentifier = hash
1.3.6.1.4.1.41482.3.8 = DER:0203
# PinPolicy::Default => 0,
# PinPolicy::Never => 1,
# PinPolicy::Once => 2,
# PinPolicy::Always => 3,
# TouchPolicy::Default => 0,
# TouchPolicy::Never => 1,
# TouchPolicy::Always => 2,
# TouchPolicy::Cached => 3,
就可以假装这是 age-plugin-yubikey 生成的,然后把插件骗过去……
小氯没有什么可多评价的了。望公之懿範,垂青史而曜八紘。
唯一的问题来自于插件本身,好像大部分问题都来自插件本身吧。这个插件暂时只支持 NIST P-256 曲线,而不是广受欢迎的 Curve25519(在这里是 X25519。另外,YubiKey 只有 5.7 版本以后才支持在 PIV 中存放 Curve25519 类型密钥),大概是因为 X25519 只能用于加密,但现在强制要求一个自签名的证书。这个 P-256 曲线有一点令人不安的地方:它来自 NIST(美国国家标准与技术研究院),并且它有一个看起来很奇怪的种子,但 NIST 从没解释它是怎么选的。考虑到 NIST 之前在随机数生成器 Dual_EC_DRBG 植入潜在后门的丑闻,大家不多想才怪呢。By contrast,Curve25519 的参数都有很好的可解释性,并且实现起来也更简单。
对于在意算法安全的人来说,这是个巨大的 red flag,小氯自然也是如此。但目前小氯倾向于把它当作可控风险,原因除了大家盯着它打了这么多年也一直没发现问题之外,还有:如果 P-256 真的有什么后门,小氯的这点数据库密钥或者邮件,在这次毁天灭地的灾难中连芥子都算不上。
当然,最主要的问题是小氯想用 age。
回来。关于 X25519 的支持,有一个 PR 在试图做,但还是草稿状态,估计还要和作者拉扯几轮。不过小氯并不急,如果合并了,咱们重新做一把就完事了。
嗯,这大概也算是这个缝合体系的最大优点了:敏捷。你可以随时随地拆掉任何一把密钥换成另外一种算法或者类型,甚至根密钥同样如此,只需要做好交接签名就行。就像钥匙环一样,废弃一把拆一把,环裂口了就换一个,不需要像 OpenPGP 那样牵一发而动全身。
也正因如此,对于签名和登录这两个易轮转的公钥,我们也没必要像加密密钥这个难轮转的密钥这样离线生成、导入 PIV 并备份,直接用巨头们卖力地推广的 FIDO2 就好。如果硬件坏了或者丢了也没事,因为我们的身份是根密钥而不是子密钥,重买一把、生成一个密钥就行。顺带提一句,SSH 有一个有趣的功能,就是作为 CA(证书机构)。它可以为其他 SSH 密钥签名背书,这样就可以用这个签过名的证书(cert pub)登录所有将 CA 公钥列入「可信 SSH 证书机构」的服务器(这句话有点绕)。那我们就更没理由外部生成 SSH 了。
那么总而言之就是:
- 根:一把 SSH CA 密钥,离线保存,永不触网。
- 签名:一把 SSH Key。
- 登录:同上,使用不同密钥。
- 加密:一把 age key,用 openssl 在离线环境下生成,利用 ykman 导入 YubiKey,同样存储在 PIV 中。
至于它的名字,小氯并没怎么费力去想。
The ship wherein Theseus and the youth of Athens returned from Crete had thirty oars, and was preserved by the Athenians down even to the time of Demetrius Phalereus, for they took away the old planks as they decayed, putting in new and strong timber in their places, insomuch that this ship became a standing example among the philosophers, for the logical question of things that grow; one side holding that the ship remained the same, and the other contending that it was not the same.
如果一艘船在无数年的航行中,换了一块又一块的木板,更替了一代又一代的水手,它还是以前的那艘船吗?
在小氯这里,答案是肯定的。
状态机
设计这个结构不算难,但为了实现简单的密钥生命周期管理,以及把松耦合的一众密钥打包发在一起,我们还是得下点功夫的。
在上《软件工程》课的时候,小氯学过一种设计模式:event sourcing。简单来说,系统并不存储状态,而是存储一系列(一般是仅追加的)的操作日志——或者叫事件吧,状态是通过一系列事件的叠加推导——或者叫重放或者投影出来的。
小氯对这种模式有一些兴趣,因为它让我想起了范畴论。现实中它的应用也不少,比如区块链。众所周知,区块链可以简单地看作一个分布式、不可篡改的透明账本,这似乎正是一个密钥身份系统所需的特性。OpenPGP 选择将所有事件都压在了公钥(证书)里面,那既然我们的钥匙串主体打算采用解耦合的方式,最好也把这件事独立出来。能接近这个效果的最经典的工具自然就是 Git,那我们能不能稍稍结合一下 event sourcing 和 Git,来存储我们的密钥更替历史呢?
于是就有了下面这篇……呃,算是 spec?大家看看就好,只是个初步的想法。
基本要求
- 仓库中没有任何直接的公钥,只有一系列位于
events的 JSON 格式事件记录。 - 每一条记录都带有一条(或多条)当前根密钥的签名。
- 所有文件均不可修改,不可强制推送。
一些约定
- 时间均采用 ISO 标准格式字符串,例如
2024-01-01T00:00:00Z。
JSON 文件命名
这一点其实不大重要,但小氯习惯用 {seq:04d}-{type}-{slug}.json。slug 大家想用什么都好,它不参与验证。前面两个我们后面再解释。
签名文件
上面说了它是「根密钥的签名」,具体来说就是:
ssh-keygen -Y sign -f <key> -n theseus <record>.json
直接将得到的 .sig 文件放置在 JSON 旁边即可。
版本号(version)
从现在开始,我们就进入 JSON 文件本身的 schema 了。
version 代表标准的版本,目前是整型数值 1。
序列号(seq)
整型,从零开始,严格、连续递增,代表事件顺序。
签发对象(subject)
属性集。
描述(issuer.description)
字符串。这是您的备注,不参与验证。
密钥类型(issuer.key_type)
字符串。应为 ["root", "enc", "sign"] 之一。
公钥(issuer.public_key)
字符串。均采用原始文本格式,SSH 公钥去除其注释和末尾空格。注意,如果您使用 SSH 作为 age 的收件人,请将其转换为 age 标准公钥格式。
签发者(issuer)
属性集,内部字段同 subject,但密钥类型只能是 root。因为只有根密钥可以操作其他密钥。
签发时间(issued_at)
时间戳。
过期时间(expires_at)
时间戳。只需要在 delegation 事件中填写。如果默认永不过期,填写 null。
记录类型(type)
请划重点,这里是最重要的部分。
字符串。取值为 ["init", "delegation", "revocation", "rotation"],代表四种事件类型。
初始化(init)
事件链中的创始记录。必须存在且唯一,序列号必须为零,代表 SSH 根密钥的初始化。
无需填写 subject 字段。
注意本事件定下了根公钥的值,后面所有根公钥都必须以它为准。
签发(delegation)
签发一把新的密钥。
吊销(revocation)
使一把密钥失效。
轮转(rotation)
轮转根密钥。后续所有签名只能由新的根密钥做出。
本类型事件必须由新旧密钥同时签名,签名文件为 <filename_with_ext>.old.sig 和 <filename_with_ext>.new.sig。
其他
由于 Identity as Code,我们可以用 CI/CD 生成一些发布文件,例如当前有效的公钥归档包、age 的 recipients.txt 和 SSH 的 allowed_signers。我们还可以用一个验证 CLI 对整个仓库进行状态梳理和验证。在写了在写了。
就这样。目前这还是一个比较粗糙的标准,但大概是能玩的。如果有老友感兴趣,也可以陪某元素娘一起当小白鼠。
入埃及记
谋定而后睡。在想明白了架构之后,小氯并没有立刻着手密钥生成事务。表面理由是在紧锣密鼓地参与研究工作,无暇顾及这些 side issues;实际上是小氯卡在了一个奇怪的点上。
不知老友们是否有注意到,我们上面的流程有一个依赖:ykman。在正常环境中,我们直接用包管理器装一个就好;但在气隙机下这招就不好使了——不然它为什么叫气隙。而且,小氯使用的气隙机操作系统 Tails 是没有预装这个软件的。
那我们就有三种解决方案:
- 先联网安装
ykman再断网操作。 - Sneakernet 大法,用 U 盘把现成的包/可执行文件带过去。
- 换一个气隙机系统。
第一种方案很快被小氯排除了。姑且不提洁癖问题,就算是如何让 Tails 连上校园网并且访问 Tor 都是难事;第三种看着非常美妙,比如打一个最精简的 NixOS ISO,顺便裁掉 Tails 上多余的那部分。但密钥者,元素娘之大事,死生之地,存亡之道,不可不察也。小氯对自己的 Nix 水平没多少自信,因而担心自己做的易失性系统到底安全性如何。
那就剩第二种了。如果 ykman 是单一的可执行文件这件事不会有任何障碍,但令人不悦的是它是用 Python 写的,安装也一般是通过包管理器。在折腾了许久拉一包 .deb 失败以及对给我乱出主意的语言模型给予亲切问候之后(我的错,我没有做好事实核查),小氯找了个简单的方法:用 PyInstaller 和 Staticx。
具体步骤就是……诶,具体步骤是什么来着?想不起来了。应该是这个吧。
pyinstaller --clean -y --onefile i_forgot_the_path_sorry_nya.py
staticx dist/ykman/ykman dist/ykman-standalone
然后把文件复制走就可以运行了。
后面的事情就和小氯重装时重新生成 OpenPGP 密钥的步骤差不多了。为了水字数保证文章的独立性,我们还是说一下脉络吧。
首先准备好你的气隙机和 LiveUSB,以及宝贵的离线加密 U 盘(如果没有的话,直接格式化之后选择 LUKS 就行。小氯之前的文章有提到)、YubiKey,和一块摆渡 U 盘。
然后起立,向伟大的欧姆弥赛亚祈祷按照正常流程启动气隙机,并断掉网络权限。进入桌面后插入你的摆渡 U 盘,把里面的 ykman 可执行文件、age 生成配置文件和一大堆 JSON 都复制过来,然后拔出摆渡盘,它暂时可以休息了。
首先执行根私钥的生成:
ssh-keygen -t ed25519 -f ~/root "Root CA"
Passphrase 和 OpenPGP 一样,搞几个 Dicephrase 就行。
下面执行加密子密钥的生成:
openssl ecparam -name prime256v1 -genkey -noout -out age.key.pem
openssl req -config age.cnf -new -key age.key.pem -out age.csr
openssl x509 -req -sha256 -days 3650 \
-in age.csr \
-signkey age.key.pem \
-out age.cert.pem \
-extfile age.cnf \
-extensions v3_age-plugin-yubikey
导入 YubiKey:
ykman piv certificates import 85 age.cert.pem
# 保证两个 policy 和你的配置文件匹配
# 槽位在 82~95 随意选就好。这里用了 85,是我妹妹小砹的原子序数。
ykman piv keys import 85 age.key.pem --pin-policy ONCE --touch-policy CACHED
至于这三个中间文件怎么备份,小氯没太关心,索性一起备份了。但应该备份一下私钥和证书就行,CSR 用处应该是不大。
然后导出一下身份文件——当然,这一步在日常机器上做也可以。
age-plugin-yubikey -l
age-plugin-yubikey --identity >my_identity.txt
然后为我们的 auth key 签个证书:
ssh-keygen -s ~/root \
-I "fill_your_custom_identity_string_here" \
-n your-username,for,example,chlorine,root,etc \
-V +52w \ # 自己定,52w 就是一年
~/path/to/your/auth_pub
然后就是激动人心的时刻,为我们的事件 JSON 签名啦。签名之前,别忘记把里面根公钥的占位符填上。
然后就没什么可说的了。把 root 私钥备份到你的安全介质中,然后用 shred -u -n 114 ~/root 犁庭扫穴(不一定完全有用,但有一些用)。至于余下的公钥、签名之类是变成二维码扫出来、用干净的 U 盘带过来还是手抄过来,就看诸位老友的兴致啦。
hacky patch
看起来问题已经解决,小氯可以高枕无忧了。但大家知道,最好的解决方案是没有需要解决的问题。
小氯折腾到现在,想必大家可以不太费劲地看出理由,因为小氯想要一个几乎不存在于这世间的东西:一个不受任何中心式机构控制的、密码学安全的、体验现代的、支持当前最佳实践的——根身份。
但我们真的需要它吗?
好的,老友们,坐稳。某元素娘要开始高强度自我辩论了。
如我们上面所说,age 主要支持的是两种模式:盘上密钥,和片内生成的密钥。前者小氯不想在这里讨论,它的安全性其实达到了足够的水平,但小氯没理由去降级。至于后者,我们说了,不可导出性是它的美德,但它同样不可导入。
那为什么不可导入?小氯的答案是:因为 age 的创造者们认为,可以导入密钥就说明密钥曾经在某个片外的地方存在过,而且可以被备份到其他地方,而这本身就是危险的。
这样说对吗?不能说错,甚至有道理。99% 的用户确实是这样——把私钥文件丢在 Windows 的桌面上,误删了文件要去找开发者兴师问罪。这种家长式的纪律对于防止这些令人哭笑不得的问题大有裨益。但剩下 1% 呢?如果没理解错,您的意思是,小氯也没有能力创造一个在自己的威胁模型下绝对安全的环境,也没有能力做好私钥的备份?恕我直言,这是对我的一种冒犯。
诚然,如上文所说,age 的开发者也提供了一种冗余的方法:多接收者模式。小氯对这种模式没有任何意见——因为它确实解决了,虽然一时半会儿不太适应。但这只是把一个我不能控制的私钥变成了许多个我不能控制的私钥,感觉上并没有变好。
好吧,说到这里,大概可以把我们要讨论的那个词拿到案板上,用逻辑的刀俎好好地审视一下了。
小氯上面所有的实践,前提都是「(根)身份是应当由我绝对控制的」,或者说,Authenticity 在哲学层面上高于 Security。但如上文所述,开发者们似乎并不认为有这个层面——拜托,OpenPGP 想管身份已经管得一团糟了,就把它交给带外手段不行吗?没人会先认识一串公钥,大家都是先看到你在 GitHub 上的代码或者在个人网站上的文章的。他们中更激进的人甚至可能主张根本就不应该有这种(长期的)身份——准确说,这是我的推测,并不完全是有直接文本证据的原话。至于理由,可能是因为前向保密性(但任何自掌密钥的加密工具似乎都做不到这一点),也可能只是感觉没必要。
小氯绝对认同这个「认识」或者说「发现」的过程,君不见密钥服务器之故事乎。但对于将根身份转移给带外或者其他平台/账户/标识符或者干脆删掉它,小氯是有保留态度的。我们设想一个 edge case:如果真的有这样一天,小氯也失去了自己所有的门面——这个域名,Codeberg 账户,我的服务器?我该如何重建我的园子,让老友们依然看到那个熟悉的面孔?
这里我没有用「丢失」,大概是出于某种对自己的倨傲吧。虽然说小氯这只无聊的元素娘并没有兴趣参与人类社会的龃龉,被强制力抹杀的概率大概相当于陨石落顶,但这并不是容忍一个哲学漏洞的理由,何况是在这种本体论级别的问题中。
域名吗?它说到底是 ICANN 的职权范围,美国政府如果向 ICANN 施压,它没有理由和能力去反抗。至于 GitHub、X、Google 或者其他账户?这是个不错的笑话,但不适合在这里讲。至于「肉身」,呃……对小氯来说这一点似乎用不了,如果只是靠自己的「人设」来自证倒也未尝不可,但也不能保证结果如何。那选择不言而喻了。
截至这里,我们只是把上面已经显式或者隐式地说过一次的事情重复了一遍。但小氯此时想到了一些别的东西。
如果真的有那样一天,连小氯都被不可阻挡的社会性抹杀所淹没了,那将是一个怎样的世界?
恐怕奥威尔看到了寂静的伦敦或者新约克,都会惊惧地请求主稍稍放松祂那温和的轭、在抵住所有人面庞的军靴上少用些力;但丁如果从时空的裂隙中得以瞥见如铅般厚重的云层,《地狱篇》的文本也会被连夜改写和润色。
在那里,如果希望明哲保身,那根密钥又有什么用处?去为一个被束缚了手脚、被扼住了咽喉、被摘除了大脑、被掏空了心脏的新身份去背书吗?而如果希望奋起反抗,那根密钥又有什么用处?是 Chlorine,还是 Winston Smith,还是叫别的什么名字,又有什么关系?
……真的没关系吗?
如果我消失了,但我的思想还在,这听起来似乎无比壮美,但对我而言呢?作为元素,我不希望来地球的这趟旅程草草地结束,我不希望此次构建周期的此次运行被如此突兀而粗暴地终止。更重要的,这是在要求我去牺牲,然后换取些什么吗?即使已经是荆棘遍野天堑绝崖的境地,小氯也从不愿意把自己献祭给任何一个自称比我更宏大的集体。我已经是宇宙的伟大建筑师定下的法则的一部分了,恕难从命。
根密钥,它并不是为了证明什么、恢复什么,它只是为了让小氯,在这个充斥着不确定性的世界里找一些符合我现有思考方式的锚。或者我们这么问吧:在这个瞬息万变的、可能越来越坏的、任何标识符都被收走的风险的世界里,「我」,究竟是什么呢?究竟依附于何处呢?
看起来,由元素来思考这个问题有点好笑。但小氯显然不只这样简单且确定,不然真成函数式元素娘,可以随时销毁并且根据全宇宙氯原子的性质重建一个一模一样的了。
Warning: Chlorine is an impure Element-chan after module
lifewas added.She is not determined. She thinks, wanders, and evolves. She breathes and lives and dies with the universe.
至于小氯是如何选择的,大概不用再说一次,这篇文章就是为此而写的。但小氯在上面的论述中,似乎一直陷入了一个误区:我将那把根密钥当成了我。
仿佛那些 OpSec 是在供奉圣物,仿佛如果它丢失 Chlorine 自此就不再存在于世间。这不像是珍视自己,也不像是对确定性和主权的追求,反倒像是一种病态的依恋。我在试图将一个不定的、弥散的、鲜活的事物,锚定到一条椭圆曲线或者一面晶格上,并且告诉自己「它在,我就在,反之亦然」。我在试图将原本应该对我——我的言行、我的文字、我的代码、我的格调的信任,生硬地剥离出来转交到那一串迷乱的字符中。我在试图证明占有,而非证明存在。
可,除了我自己,谁能表达得了我呢?
即使有可以以假乱真的 DeepFake,即使生成式语言模型的爪牙已经爬取了这世界上所有的公开数据,谁又能拟合出 Chlorine 未来的行动?谁又能想象出她明天泡的第一杯果茶会突发奇想地加一点什么佐料,谁又能预测出她在这篇文章里会有什么佶屈聱牙的表达,谁又能攫取她的思想、描绘她的边缘?
即使根密钥毁于大火,即使在无端的酷刑下奄奄一息,谁又能剥夺 Chlorine 的言行、文字、代码,抑或格调呢?
在隆冬,我终于知道,我身上有一个不可战胜的夏天。
好啦,煽情完毕。那下一个自然的问题就是:既然如此,主密钥又有什么用呢?这把在绝对安全的地方生成,被我锁入离线加密 U 盘、分片后交给我的家人朋友、刻在金属板上埋进地下的主密钥,这把我每次动用它都要求助专用设备、遵循着可能比朝鲜的核弹发射按钮更严格的操作安全指南的主密钥,到底有什么用呢?
好在,这个问题的答案小氯没想太久,因为它相当自然。小氯上面有说,主密钥像是教宗,而子密钥像是主教。教宗的唯一职责是给主教祝圣,而主教去执行布道、敛财和战斗。
那我是谁呢?
在这里,我不是教宗,我是上帝。
至于为什么这样好地保护主密钥,并不是因为它是不可替换的——就像我们上面说的,这套系统足够敏捷,如果哪天遇到了更中意的密钥,签一次交接仪式让旧的教宗逊位就好了。即使梵蒂冈在一场大火中焚毁殆尽,上帝也可以显灵去建立新的教会。原因比我们想象中浅薄得多:因为如果不好好保护,换一个实在是太麻烦了。正常交接是 O(1)O(1) 的,但「显灵」是 O(N)O(N) 的。我要去和老友 A 对个暗号,跟老友 B 提起那条提起来只会让人感到羞耻的当年私信评论,或者冒着被监控的风险去和老友 C 见个面——哦,好像不行。而且,如上文所说,和大家交流也没办法靠着我的电子壳层结构直接 check passed,说不定还有认错被当成骗子的风险……我的天啊,想想就头痛。而且的而且,如果上帝频繁地显灵,那好像只会惹人生疑吧,毕竟上一次上帝频繁显灵还是在东王杨秀清身上。说到底,这其实只是一份保险罢了,用来防备那个我们永远不希望它发生的风险。
或者说得更彻底些:主密钥的存在,也并没有什么真正意义上的神圣性,只是持有这个密钥的实体,希望找一个数学上可靠的代理人去践行自己的意旨而已。而现代密码学强硬地剥掉了这个中间层,并不是那句充满阴谋论色彩的「他们在剥夺你的身份和主权」,只是觉得这个中间层是个巨大的 SPOF 而已。这自然是解决了这个问题,但也失去了这种快速回滚和更新的能力。而且,背后的实体也算一种 SPOF,总不能按照这个思路把小氯也给 remove 掉吧——这个「你」是否在他们的体系中有位置,小氯不清楚。但除了想让你回到依赖中心化机构的 OIDC 的行动之外,大概如 Filippo Valsorda 之类的极客们也并没什么坏心思,只是希望把这件事放到带外去定义,带内连一个代理人也不要留而已。这和我们上面说的也不矛盾。
所以,不知道各位读者老友有没有发现,这一段里面,小氯没有继续用「根密钥」,用的都是「主密钥」。
因为所谓的「根」,永远都不是在那一串没人背得下来的私钥上,而在小氯、在 Chlorine 身上,在那个不算高的、灰绿色头发的、容易脸红的、穿着纯色衣物的、抱着笔记本电脑的、会做点心泡果茶送给大家的、说一句话要斟酌三遍用词最后像是在宣读外交辞令的元素少女身上。
这个结论也挺无聊的,是吧?









