Emacs 中配置 .authinfo.gpg
之前我写过一篇在 Emacs 中集成翻译插件的文章,插件依赖一些 LLM 服务,LLM 服务需要配置对应的 API key,它们是定义在 ~/.authinfo
中的。
但是 ~/.authinfo 是未加密的,如果泄露了,那么我的 API key 可能被滥用,所以我觉得还需要对它做一层加密。
这篇文章我会分享如何在 ~/.authinfo 中定义 API key、如何使用 GnuPG 对 ~/.authinfo 加密,以及如何配置 Emacs 从加密后的 ~/.authinfo.gpg
中读取 API key。
authinfo
有些连接到其他服务的 Emacs 软件包需要身份验证,由于反复提供相同的用户名和密码可能会令人烦恼,Emacs 通过 auth-source 库提供了持久化信息。
默认情况下,身份验证信息来自
~/.authinfo
或~/.authinfo.gpg
或~/.netrc
文件。这些文件的语法与 ftp 程序中的 netrc 文件类似,如下所示:
machine mymachine login myloginname password mypassword port myport
Emacs 是通过 auth-source 去实现身份验证的,一般将验证信息 (例如 API Key) 存储在这三个文件中:
- ~/.authinfo
- ~/.authinfo.gpg (加密)
- ~/.netrc
而验证信息的格式是:
machine mymachine login myloginname password mypassword port myport
其中:
- machine 服务器,DNS 名称或 IP 地址,在 auth-source-search1 查询中称为
:host
。 - login 是用户名,在 auth-source-search 查询中称为
:user
。也可以使用 login 和 account 。 - port 是连接端口或协议,在 auth-source-search 查询中称为
:port
。 - password 就是对应的密钥,或者 API key 之类的
一些例子:
machine localhost login spike port sudo password xxxx machine 192.168.100.100 login root port ssh password xxxx machine api.deepseek.com login apikey password xxxx machine api.siliconflow.cn login apikey password xxxx machine openrouter.ai login apikey password xxxx machine openrouter.ai/api login apikey password xxxx
一些插件可能会基于 machine 或者 login 去查找密钥,具体如何设置就需要看插件是如何读取的。
例如 emacs-immersive-translate 是这样读取的:
(defun immersive-translate-api-key (host user) "Lookup api key in the auth source. By default, `immersive-translate-chatgpt-host' is used as HOST and \"apikey\" as USER in Chatgpt backend. \"fanyi-api.baidu.com\" is used as HOST and `immersive-translate-baidu-appid' as USER in Baidu backend." (if-let ((secret (plist-get (car (auth-source-search :host host :user user :require '(:secret))) :secret))) (if (functionp secret) (encode-coding-string (funcall secret) 'utf-8) secret) (user-error "No %s found in the auth source" user)))
immersive-translate-chatgpt.el 中设置 ChatGPT 的密钥:
;; ...其他代码 ("Authorization" . ,(concat "Bearer " (immersive-translate-api-key immersive-translate-chatgpt-host "apikey")))))) ;; ...其他代码
可以看到,emacs-immersive-translate 基于 :host 和 :user 去查找的:
:host
(machine) 是immersive-translate-chatgpt-host
,默认是 api.openai.com:user
(login) 是apikey
所以按照默认配置,当你获取到 openai 的 API key 后,要这样配置:
machine api.openai.com login apikey password your_openai_api_key
如果你只是希望配置好 API key 给插件用,你只需要将 API key 按照插件要求定义在 ~/.authinfo 中,就足够了。
如果你想共享 ~/.authinfo,例如分享给特定的人,推送到公开的 git 仓库,同步到云盘,你不想所有人都能看到你的密钥,那你可能需要进一步对文件进行加密。
GnuPG 加密
我对 GnuPG 研究得不多,简单分享一下如何创建 gpg 密钥,如何加密文件。
创建 gpg 密钥
执行
gpg --full-generate-key
,按照提示完成创建即可。创建完成后可以通过
gpg --list-keys
查看当前得密钥,例如我执行后会得到:/home/spike/.gnupg/pubring.kbx ------------------------------ pub rsa4096 2025-02-27 [SC] 1BAF5568B6AFD3DB367B865B50AC4C6F1FD4B1CF uid [ultimate] SpikeLeung <l-yanlei@hotmail.com> sub rsa4096 2025-02-27 [E]
其中:
1BAF5568B6...4B1CF
是指纹(fingerprint)2[ultimate] SpikeLeung <l-yanlei@hotmail.com>
是 uidl-yanlei@hotmail.com
是和这个 gpg 密钥关联的邮箱
加密文件
gpg -e -r "SpikeLeung" ~/.authinfo
或者gpg -e -r "l-yanlei@hotmail.com" ~/.authinfo
其中,
-e
表示加密,-r
指定使用哪个 user-id,对应的是上面 uid 中的内容,你可以用名字或者邮箱。加密成功的话,就会在当前目录生成一个带有
.gpg
后缀的加密文件~/.authinfo.gpg
。
配置 Emacs 读取 ~/.authinfo.gpg
原来 ~/.authinfo 是未加密的,Emacs 里可以随便访问,现在加密了,那么在访问前就要先解密才能访问了。
为了让 Emacs 能够解密 gpg 加密的文件,我们需要让 Emacs 能够读取 gpg 密钥,然后用密钥去解密。
Emacs 内置的 EasyPG Assistant3 可以帮你自动完成加密解密,当需要访问 gpg 加密文件的时候它就会启用,你不需要额外做什么。
因为 gpg 密钥我也设置了密码,打开 Emacs 还需要先输入密码,让 gpg 密钥能被访问。
默认输入密码是弹窗输入,输入的时候很容易卡顿,可以添加下面的配置:
(setq epa-pinentry-mode 'loopback) ;; 在 minibuffer 输入密码
这样 Emacs 会从 minibuffer 读取密码,输入更流畅一些。
Bonus: The Unix password store (pass)
除了 EasyPG Assistant,你也可以用 The Unix password store (pass),这是一个简单的密码管理程序。
如果你有在多个设备管理密码,或者有多个 gpg 密钥需要管理,或许你可以考虑用 pass。
pass 更侧重于密码管理,可以方便地在多个设备管理密钥,Emacs 也可以和 pass 集成。
如果你的环境中没有 pass,首先你需要按照文档安装好。
然后初始化 pass: pass init gpg-id
,这里的 gpg-id 可以是:
- fingerprint:
1BAF5568B6...4B1CF
,你可以完整输入一长串,好像也可以只输入前几位,和 git hash 有点类似 - uid:
SpikeLeung
或者l-yanlei@hotmail.com
这样,pass 就配置好了。
接下来就是告诉 Emacs 启用 pass,在配置文件中添加一下配置即可:
(auth-source-pass-enable)
写在最后
我对 GnuPG 其实不太熟悉,文章中如果有什么错误,欢迎留言指正~
脚注:
auth-source-search 是 Emacs 中的一个函数,可以基于条件查询返回基于 auth-source 定义的密钥。
更详细的解释可以看 Anatomy of a GPG Key。
在 Emacs 中你可以通过 C-h R epa
查看相关文档。