目录

使用 SSH 代替 GPG 密钥对

我吊销了我的 GPG 密钥,并从今天开始全面切换到基于 SSH 密钥对的方案。这篇文章会解释 How & Why。

1. 复杂的 GPG

在最开始,我们必须承认,GPG 确实是安全的,而且在承诺的三个领域,一直表现得还凑合。

那 GPG 的问题在于哪儿呢?它太老了。由于它太老,GPG 有非常多的累积问题:

  • 历史包袱繁多,比如因为各种已经说不清的原因,光包长度在 GPG 种就有至少 2^3 = 8编码方式
  • 新旧社区分化:2022 年仍然有教程在推荐使用 RSA 算法来生成 Key,即使在 best practice(包括默认)都强烈推荐使用 Ed25519(ECC + Curve25519) 生成新的 Key。
  • GPG 因为各种兼容性问题,在操作中支持非常多已经废弃,甚至在现代是认为已经失效的密码学原语,这让整体操作和维护难度直线上升。

如果你想要用好 GPG,你需要先成为一个密码学“专家”:搞清何为非对称加密只是入门课,你还需要知道 4 种密钥类型是干什么用的,为什么直接使用 Certify 类型的密钥不安全,ASE 三种类型究竟是什么场景,吊销密钥又是一个什么过程。而即使你已经成为了密码学高手,在操作复杂的 GPG cli 过程中,你还是会经常遇到各种操作失误导致的失败配置,以及解决调配各种 GPG 的环境的麻烦。是的,GPG 套件也跟 OpenSSL 一样,非常难“用对”。

提示

如果你对为什么我不再喜欢 GPG 感兴趣,而恰好你又有一定程度的密码学基础,可以阅读下面的文章:

我同样建议阅读对立面的,对 Latacora 批评的反驳: https://articles.59.ca/doku.php?id=pgpfan:tpp

而实际上,我们需要使用 GPG,核心就是为了满足上面提到的三个诉求:加密(Encryption),签名(Sign)和认证(Authentication)。幸运的是,现在是 2024 年,我们在现代密码学的加持下,已经有了完美支持上面三个需求,且很难用错的基础设施:SSH。

本文就将探讨如何使用 SSH 来达成前两个效果。由于认证是 SSH 协议的基石,本文不再详细展开。

提示
本文假定你已经有一个 SSH 的密钥对,强烈推荐使用 Ed25519 算法生成,并且不要将其混用在其他场景里面,比如当做跟其他服务器认证的密钥对。

2.1. 签名

一切的开始,首先确定你的 OpenSSH 版本超过 8.0:

1
2
ssh -V
# OpenSSH_9.7p1, OpenSSL 3.2.1 30 Jan 2024

如果你使用的是现代操作系统(包括 Windows 10+),你应该都有一个符合要求的 OpenSSH 版本。

签名的过程非常简单,假定你需要对 about.txt 进行签名,你可以直接执行:

1
2
3
ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n file about.txt
# Signing file about.txt
# Write signature to about.txt.sig

其中,-f 指向了用于签名的私钥,-n 是一个任意字符串,用于指定文件的命名空间。为何要使用命名空间?防止有人对签名做篡改。

如果你点开签名文件 about.txt.sig,你会看到类似于如下的内容:

1
2
3
4
5
6
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg0gegi7ksx4Wj0g29sKt9bG50Rs
Rgn18NF9Lp4UlFDtgAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
OQAAAEA6Y2YjN/HJQ89C4ZcV5xW816dX0hpSA/xxesYHGEZq+4IAFKskUvrZc+FoziUAAq
A+BJv9/1wUwtYsflxmiiIJ
-----END SSH SIGNATURE-----

验证这个签名也非常简单,可以使用:

1
2
ssh-keygen -Y verify -f signer -I test -n file -s about.txt.sig < about.txt
# Good "file" signature for test with ED25519 key SHA256:cSJQj6N+88sROdiFp3ZOMALL9kjMD98stwlJZNszthE

其中,你需要提供一个 allowed_signer 文件,维护<signer> <public key>的映射,类似于:

1
test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINIHoIu5LMeFo9INvbCrfWxudEbEYJ9fDRfS6eFJRQ7Y

然后在 -f 指定文件,-I 指定签名者,就可以进行验证了。你可以尝试调整 -I-n 或者修改文件和签名,看看不同情况的输出如何。

如果需要自定义签名验证流程的话,格式可以参考官方描述,可以看到这个签名的格式非常简单,解析和验证应该较为简单。

特别地,Git 也能使用 SSH 密钥进行签名了,首先先确定你的 Git 版本 >= 2.34,然后参考 Github 的教程 进行配置。至于如何在各个托管仓库中进行展示,可以自行寻找教程,比如 Github 的教程在

2.2. 加密

虽然我们可以使用 OpenSSL 来加密,但是我推荐使用 age 来完成这项工作。

age 是一个非常现代的工具,所有加密原语都是(至少截至 2024-04-13)业界最好的,同时标准里面的设计也基本都是业界最佳实践。

age 的使用很简单,我就不在本文过多赘述了,感兴趣的欢迎自行查看项目主页


同样,根据惯例,我也会在本文发布我的公钥。欢迎使用上面提到的方式使用 SSH 公钥给我发一封邮件。

1
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOku75Ulw2Fou7hvZQOZm2YN4cM8YRBYbZviIlkvu9jq

这也是我用于签名 Git commit 的密钥,所以你也可以在 https://api.github.com/users/lxdlam/ssh_signing_keys 看到。