我正在开发一个存储和检索秘密的计算机程序,并且需要在服务器上无需用户交互即可运行。
我已经成功设置了系统中的所有内容,用于gpg-agent
检索缓存的密码,无需用户交互即可加密和解密数据gpg2 --symmetric
当我指定文件名时。
但是,这要求我将数据临时存储在磁盘上,并且不加密。我不想将未加密的数据放在磁盘上。所以现在我已将其更改为使用 STDIN 和 STDOUT 而不是磁盘文件。
我正在测试的命令是:cat test.txt | gpg2 --symmetric -o test.gpg
。
问题是现在似乎无法缓存密码,因此每次我都必须以交互方式输入。这对我的服务器程序不起作用。
我曾尝试使用 gpg-preset-passphrase 来实现这一点。但我不知道这是否可行,因为它需要一个“KEYGRIP”。我有一个粗略的想法:也许 gpg2 会使用 --symmetric 和 STDIN 来设置默认的 keygrip。但我不知道这个想法是否会成功。
我不打算使用 --passphrase 在代码中指定密码,因为这意味着我需要在代码中存储秘密,而我不会这样做……而且这会允许任何可以访问代码的人解密文件。它还会在命令行上暴露密码。
我希望我想要实现的目标足够清晰。任何想法都值得赞赏。
答案1
我最近一直在努力解决这个问题(在 gpg 文档和 qa 网站上无休止地摸索之后)遇到了这个2015 主题提到答案:
也许我遗漏了在创建和重用时如何为对称密码选择“cache_id”。你能总结一下吗?我在代码中挖了一些,但没有弄清楚它是如何完成的。
如果我没记错的话(并且逻辑没有改变),它是一个随机选择的 8 个八位字节的盐值: https://www.rfc-editor.org/rfc/rfc4880#section-3.7.1.2
...
S2K 盐被用作缓存 ID。
您可以通过列出加密文件的数据包来获取盐值:
$ echo | gpg --list-packets ~/myencryptedfile 2>&1 | grep -o '[0-9A-F]\{16\}'
0123456789ABCDEF
- 主导
echo
力量Inappropriate ioctl for device
错误,绕过 pinentry 提示 2>&1
隐藏 stderrgrep -o '[0-9A-F]\{16\}'
从命令输出中提取盐
现在我们有了8个八位字节的十六进制编码的盐值。
不幸的是,这个方法对我来说不起作用。所以在 gpg 代码库中摸索了一段时间后,我遇到了此片段:
memset (s2k_cacheidbuf, 0, sizeof s2k_cacheidbuf);
*s2k_cacheidbuf = 'S';
bin2hex (s2k->salt, 8, s2k_cacheidbuf + 1);
s2k_cacheid = s2k_cacheidbuf;
关键是第二行前面的“S”。我没在其他地方找到这个说法。
现在就全部搞定。加密文件:
$ gpg --symmetric --passphrase-fd 3 --batch --output ~/myencryptedfile <<EOF 3<<EOF3
> myencryptedcontent
> EOF
> mypassphrase
> EOF3
--passphrase-fd 3
:通过文件描述符 3 提供密码--batch
: 必填项--passphrase-fd
- 我更喜欢通过
--passphrase-fd 3
stdin 提供密码和要加密的数据(<<EOF
和3<<EOF3
),而不是最初将其写入文件cat
,或将其回显到 stdin(这让我担心进程列表窥探)。文档:此处文件(EOF 语法)
。Bash此处字符串在语法上更易于接受(例如gpg --symmetric ... ~/myencryptedfile <<<myencryptedcontent 3<<<mypassphrase
),但会在 bash 历史记录中留下密码,因此在这种用例中最好避免使用它们。
获取 8 个八位字节的十六进制盐值:
$ echo | gpg --list-packets ~/myencryptedfile 2>&1 | grep -o '[0-9A-F]\{16\}'
0123456789ABCDEF
确保 gpg-agent 允许密码重置:
$ cat ~/.gnupg/gpg-agent.conf
allow-preset-passphrase
$ gpg-connect-agent reloadagent /bye # restart the agent after changing config
预设密码:
$ /usr/local/Cellar/gnupg/2.2.17/libexec/gpg-preset-passphrase --preset S0123456789ABCDEF <<EOF
> mypassphrase
> EOF
- 在盐前面加上“S”,因此生成的 gpg-agent cacheid 是
--preset S0123456789ABCDEF
- 通过 heredoc (EOF) 在 stdin 上传递密码,以避免它出现在进程列表中
- 如果出现
ERR 67108924 Not supported <GPG Agent> - no --allow-preset-passphrase
,则需要添加allow-preset-passphrase
并~/.gnupg/gpg-agent.conf
重新启动代理(参见上文) - 在您的系统上的位置
gpg-preset-passphrase
可能不同。
最后,解密文件!
$ gpg --decrypt --pinentry-mode loopback ./myencryptedfile
gpg: AES encrypted data
gpg: encrypted with 1 passphrase
myencryptedcontent
笔记:
- 我发现
gpg-preset-passphrase
在我的脚本中定位很烦人,因为它没有安装在系统路径的任何地方(至少在我的安装中没有)。但是,gpg-connect-agent
是的,并且可以用于相同的目的,但需要额外的步骤。您只需要在使用密码之前对其进行十六进制编码(tr -d '\n'
因为 EOF 添加了换行符):
$ (tr -d '\n' | hexdump -v -e '/1 "%02X"' && echo) <<EOF
> mypassphrase
> EOF
6D7970617373706872617365
$ gpg-connect-agent <<EOF
> preset_passphrase S0123456789ABCDEF -1 6D7970617373706872617365
> EOF
OK
# now decryption should work as above
- 我在 OSX 上运行 gnupg 版本 2.2.17,通过 homebrew 安装
$ gpg --version
gpg (GnuPG) 2.2.17
libgcrypt 1.8.5