index | ~dongdigua

一位 PGP 进步青年的 Canokey 历程

Table of Contents

作为一个注重网络隐私进步青年, 当然要使用 PGP 以提升逼格.

而为了追求极端的安全性, 就不能把私钥明晃晃地摆在电脑里. 这时, 就需要使用与电脑分离硬件密钥, 来防备那些妄想中可能窥探你电脑的敌人.
当然, 我使用硬件密钥还因为我同时使用 Linux 和 OpenBSD, 使用硬件使共享密钥更容易, 因为这一份私钥就代表了你在网络上的身份.

Yubikey 太贵, 所以选择咱国产的 Canokey.
我看见的几乎每一篇文章都会长篇大论地讲最基本的使用, 好像你能比 manual 或 wiki 讲的更明白更全面似的, 关于基本使用请看文章末尾推荐的几篇好文章
我不想重复讲这些, instead, 我想讲讲我使用 Canokey 过程中遇到的问题:

1. 设定触控 (Touch Policy)

文档是这么说的:

You may turn ON or OFF touch policies for SIG, DEC, AUT in the admin applet in the web console or via the gpg command.

事实上

  • 我试了 gpg 命令, 翻遍了整个 gpg(1) manpage, 搜索(with keyword "touch")了整个 GPG 文档, 也没找到 gpg 里面能开启触控的地方.
  • 然后打开 Chromium, 使用那个 suspicious(当然那个页面没什么可疑的, 只不过我对使用联网应用操作这种设备感觉不舒服) 的 web console, 结果也不好使, 因为什么 255.
  • 然后使用 ykman -r Canokey, 不好使, 然后运行了他们 fork 的 yubikey-manager (加到我的nur 仓库了), 经过漫长的 poetry 构建, 好使了.

准确来说, 是运行第一次不好使, 第二次之后就好了, 并且 web console 之后也好使了…

文档似乎造成了一些迷惑, 我提了一个 pr 修复这件事, 但是这个项目自从去年8月份就没什么进展了.
顺便说一嘴, 我注意到 GitHub 上好多项目都在 Nov 2022 归档了, 这似乎不是个别的现象, 那时发生了什么事情吗? 有人能解释一下吗?

pr 太尬了, 人家(以及文档)告诉我用 uif 能修改, 然而我当时没注意. 现在翻了下源码:

/*
  include/openpgp.h
*/
#define TAG_UIF_SIG 0xD6
#define TAG_UIF_DEC 0xD7
#define TAG_UIF_AUT 0xD8
#define TAG_UIF_CACHE_TIME 0x0102

/*
  applets/openpgp/openpgp.c
*/
static int UIF_TO_TOUCH_POLICY[3] = {[UIF_DISABLED] = TOUCH_POLICY_DEFAULT,
                                     [UIF_ENABLED] = TOUCH_POLICY_CACHED,
                                     [UIF_PERMANENTLY] = TOUCH_POLICY_PERMANENT};

然后 gpg-card 里说的是 "button", 我搜索 "touch" 就忽略掉了…
以及 Functional Specification of the OpenPGP application on ISO Smart Card Operating Systems 的 4.4.3.6 节

好吧, 虽然 pr 弄的很尴尬, 但通过提问总算是解决了问题, 否则我可能会一直陷在我的思维模式里也想不明白.

2. 日常使用子密钥

我日常会使用 gpg 加密一些配置文件里的东西, 但是我不可能每次想读邮件都插上硬件密钥, 那就太费劲了, 所以我想使用另外一个加密子密钥来做这件事,
这样给我的感觉是有一个特别安全的主钥匙串, 上面挂着一些不太安全的子钥匙, 但是有些地方使我困惑:

  • PGP 会默认使用所有子密钥公钥中最新的一个来加密, 所以我导出公钥的时候就要去掉其中的子密钥而只保留生成密钥时附带的加密子密钥, which is safe on the Canokey
  • 这个子密钥由于放在电脑上, 不能保证安全, 所以不能让别人用它的公钥给我加密, 所以这上的身份信息没有意义了
  • PGP 的子密钥会继承主密钥的 UID 而不会有自己的 UID, 所以当我加密的时候就是用的主 UID, 而我的想法是区分不同的 UID 来加密

所以经过思考, 我还是重新生成一对密钥来日常加密吧, 就像我曾经一直在用的方式, 只不过之前日常的密钥就是我公布出去的公钥, 本地有多个密钥也不会造成什么混乱.

我对加密子密钥和签名子密钥的理解

可以有多个签名子密钥并公布相应的公钥, 但加密公钥应该公布那个可以保证安全那个子密钥的公钥, 当然, 最好保证所有密钥的安全.

  • 如果签名子密钥泄漏, 你可以吊销使其作废, 使该子密钥签过或将来签的名全部作废, 你的签名信誉也不会受影响.
  • 而如果加密子密钥泄漏, 所有已经存在的使用该公钥加密的文件都可以被解密, 吊销只能使别人不再使用次密钥给你加密, 阻止不了使用私钥解密, 也大概会影响信誉.

所以 Debian 讲 Subkey 时提到的情形也只是签名, 而不是加密. 签名也是 WOT 的基础, 而加密是另一码事.
那么是否和何时使用加密子密钥有什么讲究吗?
Security StackExchange: Utility of multiple signing subkeys when we're restricted to a single encryption subkey in GnuPG (PGP)

不同机器?

具体请看下方 Debian 文章的 "Caveats: Multiple Subkeys per Machine vs. One Single Subkey for All Machines"
不同机器使用相同日常密钥倒是能使交换文件更方便, 但是如果一个机器被泄漏也会影响到那些机器的文件, 再去挨个更换也是费劲.
至于保证不同机器导出的公钥是全的, 都导入公钥就行了.

既然我有一个安全的密钥在不同机器之间共享, 我需要交换的文件可以使用共用的密钥加密, 也方便我 rotate encryption.
但是, 我日常使用需要忽略硬件密钥对应的私钥, 否则它总会尝试使用那个私钥解密, 何如?
哦! 重启一下 gpg-agent 就好了, 能在智能卡不在的时候用本地的私钥解密, 而不是要求我插入智能卡.

这很清晰了吗? 见下文 gpg.conf 以及 age 的讨论

靓号?

倒是应该生成一个靓号用来做签名子密钥, 而不是使用主密钥签名.
vanity_gpg 使用 sequoia(fedora 现在也使用 sq 了) 作为后端, 通过修改时间戳来快速改变密钥生成.
时间戳是向过去修改的, 大概是为了防止 gpg: key X was created Y seconds in the future (time warp or clock problem) 这种警告
但是, 作为子密钥, 时间必须在主密钥之后(见下文), 所以, 应该让向过去走的时间有一个限度.
那么就改源码吧!
稍微看了一下 main() 函数, 发现前面有一个常量

/// Key reshuffle limit
const KEY_RESHUFFLE_LIMIT: usize = 60 * 60 * 24 * 30; // One month ago at worst

/* ... */

} else if reshuffle_counter == 0 {
    info!(
        "({}): Reshuffle limit reached, generating new primary key",
        thread_id
    );
    key = Key::new(DefaultBackend::new(cipher_suite.clone()).unwrap());
    reshuffle_counter = KEY_RESHUFFLE_LIMIT;

作者也考虑到了, 如果时间过早就重新生成, 限度是一个月. 拖了这么长时间, 也差不多一个月了, 所以在时间上没问题了.

那就生成一个, 开始缝合, 一切顺利.
不过, 当我准备删除临时导入的私钥时, 问题出现了, 而我也知道了之前主私钥是怎么丢失的了!
当我试图删除导入为主私钥的靓号时, 会同时删除以靓号作为子密钥的那个主密钥,
因为那两个靓号密钥是等同的, 是一个密钥的 keyrip 同时作为主密钥和子密钥, 还好我手里的主密钥是放在 canokey 上的.
所以就需要先把整个私钥导出, 再删除靓号主私钥, 再导入回来, 就 ok 啦. ()
还是 Debian Sunkey 那篇里面说的, 直接删 ~/.gnupg/private-keys-v1.d/ 对应的 keyrip 就行啦

靓号我先用着一个没花太长时间就生成的(regxp:AAA520$), 之后感觉可以时再多花点时间生成个好的.
candidate: 66CCFF

3. gpg.conf 和 gpg-agent.conf   negative

主要想弄一个事, 就是输入密码的时候不弹出窗口, 而是用终端界面, 看起来更 hack 一些.
这个是在 gpg-agent.conf 里 pinentry-program /usr/bin/pinentry-curses (有些系统上叫 pinentry-tty)

而我用来验证我使用的是正确的签名密钥时:

[~]── ─ echo aaa | gpg --clearsign | gpg --verify
gpg: signing failed: Inappropriate ioctl for device
gpg: [stdin]: clear-sign failed: Inappropriate ioctl for device

是因为我硬件密钥的签名子密钥设置了密码, 而密码使用的是终端输入, 所以 stdin 无法输入密码…
包括 git commit -S 的时候也会输入不了密码, 所以还是改回去吧, 用弹出窗口…
(而 git 使用 ssh 签名的时候就能很好的处理终端密码输入)

gpg.conf 我没放太多东西:
default-key 倒是能指定硬件密钥的子密钥而非本地的另一个主密钥来签名, 但是解密的时候却也优先使用硬件的密钥, which 我刚弄明白怎么样才能不使用它.
而 local-user 则解决了这个问题, 吗? -u 是可以覆写 default-key 的, 但是没人能覆写它, 而且再加 -u 不会替代 gpg.conf 里的 local-user, 而是都加上.
这就是我之前对同时持有多个主密钥及绑定的子密钥这件事的担忧, 会变得混乱.
一个比较激进的做法是直接移走日常加密的主密钥来防止被用于签名, 反正我也用不到.
但是, (友好的问候语) gpg 还会尝试使用已经删除的私钥进行加密!!! 彻底服了…

4. age   negative

加密改用 age 吧, stateless 比 stateful 容易多了 (stateless 另一个例子就是 sequoia-cli)
age 这种小工具, 以及下文提到的 signify, 都是比较接近 UNIX 哲学的, 而 gpg, 则是 GNU 软件那种复杂的全能的瑞士军刀.
反正无论 gpg 还是 age, 如果没有保护的私钥放在本地, 安全性都是差不多. gpg 还是用来签名和 WOT 更有用一些…
Xe 也受不了 gpg 改用 age 加密了: I fucked it up, this key is broken
当然, 我这里暂时先不公布 age 的公钥, 还是因为私钥在硬盘上, 这是对发送加密消息的对方的不负责.
如果之后公布的话, age 的公钥弄成 vanity 能更有趣, 因为收件人recipient能包含更多字母.
candidate: rtx3090

5. pass

首先, 密码的准则: https://xkcd.com/936/

这种密码管理器直接用明文目录结构存储密码, 会泄漏元数据啊!
更糟糕的是如果放到 GitHub repo (比如 gopass), 就相当于告诉人家你都有什么账号以及什么时候更改了哪个账号的密码.
而我的想法是, 须要分文件存储而不是使用整个一个加密文件, 但文件名不能泄漏元数据.
应该让密钥的安全性作为最后的防线, 而不只是保证文件不泄漏, 在这之前不要透露任何额外信息.

但是当我开始写的时候发现一个大问题: gpg 加密每一次结果都不一样…
那么就不能进行双向的 (用户查询加密后匹配), 只能把所有名字都解密再匹配.

6. SSH/硬件

age 能用 SSH 密钥, 很棒.
不过, -sk 的 FIDO 硬件验证密钥, which 能提高一些安全性, 不能用于 age 加密, age 本身的FIDO 支持还不完善, age 的 piv 不能用 Canokey.
所以硬件支持方面还是 PGP 更好…

那我就试试能不能加入这个特性呗!
首先加密很容易, 就是从公钥中抽出密码学的部分, 然后就和一般的 ed25519 一样了.
解密就费劲了…

7. 2FA

github 弄 2FA 了
我试图用 canokey 做 oath, 还是只有 ckman 能用. yubico authenticator Linux 版无法使用 custom reader, chrome 能用 web console.
使用 FIDO 需要设置 PIN, 但是 ckman 无法设置, 文档建议用 Windows Hello, 行.
这些验证方式都好方便啊! 希望在未来互些技术能普及到更多人, 也希望各平台能更加统一.

8. 缝合曾经的主密钥

之前其实早就想弄硬件密钥了, 但是一直没有什么事情驱使我去做, 直到有一天, 我导入靓号(又想删除)的时候, 一不小心把我的主密钥删了, 大概是 fish 补全的锅 原因见上.
所以我才想重新生成一个密钥并且保证安全. 但是后来又想到, 我实际上之前大备份的时候有我主目录的备份, 也有我那时后的私钥, 有希望啊!

可行性?

我就想把曾经那个密钥缝合到现在的密钥环上作为子密钥, 但是之前在某科学的 PGP 算号指南里看见

在缝合密钥的时候,有个大前提:主密钥的生成时间必须比子密钥要早。因此对于上面的一组待缝合密钥,只有生成时间最早的那个「靓号」可以做为主密钥。

显然, 我之前的密钥比现在这个早, 那会出现什么问题呢? 人家没说…
难道就没有可能吗? 那个文章引用的 Security StackExchange: Migrating GPG master keys as subkeys to new master key 是十年前的了, 而且过于复杂.
(其中提到的老教程存档于互联网档案馆: http://atom.smasher.org/gpg/gpg-migrate.txt 使用 GnuPG v1)

那讨论里面说了, GnuPG 2.1 之后可以把任何密钥变成子密钥, 但 是, 直接加会改变子密钥的指纹!
所以要使用 --faked-system-time=timestamp!, 如果子密钥时间更早, 的确能成功加上, 但 是, 主密钥的时间会变成最早子密钥的时间, 产生警告!

gpg: public key B8B791E307A9887E is 17 days newer than the signature
sec   ed25519/B8B791E307A9887E 2023-04-16 [SC] [expires: 2025-04-15]
      54E849C81A511739C6A12D23B8B791E307A9887E
      Keygrip = 306F8BD727C402801BCF773F4BB367CCF8B3D017
uid                 [ultimate] testmigrate
ssb   cv25519/18A470DFAFA4339C 2023-04-16 [E] [expires: 2025-04-15]
      Keygrip = 053B88E19B5839C7A6549237E4ADA01F106CA026
ssb   ed25519/0D8DD61234B1287A 2023-03-29 []
      Keygrip = A110196057DDA134F4360662936EB5AE4F337B33

sec   ed25519/0D8DD61234B1287A 2023-03-29 [SC]
      996AAA92AB43EE992005A7A50D8DD61234B1287A
      Keygrip = A110196057DDA134F4360662936EB5AE4F337B33
uid                 [ unknown] earlier
ssb   cv25519/28905D27051C7D61 2023-03-29 [E]
      Keygrip = 8A99C8A1406C9A3A3EA2D40F1637A5F4D3415FA8

对比加入老密钥作为子密钥之前和之后比较 gpg -K --with-colons 的输出, 会发现实际上主密钥本身的时间戳并没有改动, 只是加了一个时间戳更早的子密钥.
所以推断, --faked-system-time 并不会玩坏主密钥的时间, gpg 是根据最早的时间(不管是主密钥还是子密钥)作为一个密钥的时间, 但是会与主密钥的时间进行对比, 产生警告.

diff -u --label \#\<buffer\ bbb\> --label \#\<buffer\ aaa\> /tmp/buffer-content-6AjMJE /tmp/buffer-content-yx7Sxg
--- #<buffer bbb>
+++ #<buffer aaa>
@@ -12,6 +13,9 @@
 ssb:u:255:18:18A470DFAFA4339C:1681624598:1744696598:::::e:::+::cv25519::
 fpr:::::::::FE55371FE6BB6C7B93DEA6FB18A470DFAFA4339C:
 grp:::::::::053B88E19B5839C7A6549237E4ADA01F106CA026:
+ssb:i:255:22:0D8DD61234B1287A:1680094463:::::::::+::ed25519::
+fpr:::::::::996AAA92AB43EE992005A7A50D8DD61234B1287A:
+grp:::::::::A110196057DDA134F4360662936EB5AE4F337B33:
 sec:-:255:22:0D8DD61234B1287A:1680094463:::-:::scESC:::+::ed25519:::0:
 fpr:::::::::996AAA92AB43EE992005A7A50D8DD61234B1287A:
 grp:::::::::A110196057DDA134F4360662936EB5AE4F337B33:

所以还是算了吧? 这种警告挺烦人的

意义

其实我就是想能不作为主密钥的形式让曾经的私钥复活, 并且一同放在导出的公钥, 用于验证我之前的签名.
不过, 我之前似乎也没签过几次名, 即使有, 也有一些被 git rebase 覆写掉了.
所以之前的那个主密钥没多大意义, 扔了得了, GitHub 上面也要删了, 不然 https://github.com/dongdigua.gpg 还是之前那个.

-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: This is a revocation certificate

iJwEIBYKAEQWIQRWzuFXyfUzT8pSzsPRhOgogGKQtgUCZE8SMSYdAWFuZCBpdCdz
IG5vdCBjb25zaWRlcmVkIHNhZmUgYW55bW9yZQAKCRDRhOgogGKQthfeAQDKFM3d
CPH1XPIdHlVomS60QYOnCQqx94tZZHoDXiF6ugEA5EV5mQ1L8kFJErUxF+66VFg/
pBxFcd/TsbHah3HNowc=
=YTGm
-----END PGP PUBLIC KEY BLOCK-----

9. 极短的签名?

我在调查 openbsd-misc 上的 PGP 使用情况时发现有人签名比我三行多一点的 Ed25519 还要短?!
UlyC 说 ECC 的优势之一就是比 RSA 短, 那谁还比 ECC 更短呢?
开个 voidlinux 的 chroot (避免污染 stateful 环境) 做了点实验

RSA > ED25519 > secp256k1 = DSA2048 > DSA1024
>5l   3l+       2.5l+       2.5l+     2l+

(secp256k1 是比特币的算法) (1024位的显然不应该使用)

10. Outro

最近脑子里都是这些东西, 不少是在学校拿草纸写的. 感冒在家, 身体停下了脑子也停不下, 一直在完善这个草稿.
总算把一直盘旋在脑子里的思路理得差不多清楚了!
这只是一个刚研究 PGP 两周的无聊高中牲的一些想法, 肯定有诸多不足与错误, 欢迎给我发邮件或提 issue 讨论.

近几天我在各搜索引擎里的东西全是 gpg, 密码学, 隐私, (甚至还有 CITIZEN4), 等相关的东西, 也在担心是否会被盯上.
即使我学习使用这些加密手段, 也还是不可避免的在正常浏览中要遭受追踪和审察.

开发者们创造各种软件为了让互联网更安全, 但是技术还是拯救不了愚蠢的人.

到这里, 我听到有人在问 「这是不是有病?」
是的
可是,亲爱的朋友,一个没病的人又怎么会去使用 PGP 呢?

11. 好文章

DuckDuckGo 比 Bing 的搜索结果质量高多了

Debian Wiki 系列

 .''`.  ** Debian GNU/Linux **
: :' :      The  universal
`. `'      Operating System
  `-    http://www.debian.org/

因为 Debian 的开发高度依赖 PGP, 所以有很多不错的文章很好的解释了 GnuPG
Debian 将 PGP 形容为 "This is your source of power", 感觉他们好传统啊, 相比之下, Fedora 的开发方式被大公司带的更现代.

在我收藏夹中的

2021年, 用更现代的方法使用PGP (上中下)

世界上有两种密码: 一种是防止你的小妹妹偷看你的文件; 另一种是防止当局阅读你的文件.
--Applied Cryptography

other

OpenPGP Best Practices

被很多人乃至 Debian Wiki 放到相关链接

LCTT: 用 PGP 保护代码完整性 (一~七)

一系列详细的教程, 翻译的不错

LWN: Future directions for PGP

both NetPGP and NeoPG are died
I tried to build netpgp, but too many errors!

archbbs: 个人使用如何选择GnuPG密钥方案?使用子密钥还是不用?

世界上有两种密钥信任体系,PGP 的 web of trust 和 TLS 证书的 CA。前者是社交化的,相信你所相信的人。后者是相信权威。
– 依云

dongdigua CC BY-NC-SA 禁止转载到私域(公众号,非自己托管的博客等)

Date: 2023-04-16 Sun 00:00 Build: 2024-04-04 Thu 05:51

Proudly made with Emacs Org mode

Email me to add comment