使用 Bash 破解保险箱
多年来,我一直坚持将我的敏感数据保存在纯文本文件中,然后对其进行非对称加密。尽管我注意加强我的系统安全,并在可能的情况下使用 LUKS 加密分区,但我希望使用更高级别的工具来保护我最重要的数据,从而减少对底层系统配置的依赖。在这个领域中存在许多强大的工具和实用程序,但有些工具在某种程度上引入了不可接受的“臃肿”。作为一个极简主义者,我对处理会减慢我的工作流程的 GUI 应用程序或仅适用于我的部分敏感数据的特定于应用程序的解决方案(例如浏览器密码库)几乎没有兴趣。使用文本文件可以更灵活地控制我的数据结构,并提供利用我几乎在任何地方都能找到的标准工具的能力。
非对称加密非对称加密,或公钥密码术,依赖于两个密钥的使用:其中一个密钥是私有的,而另一个密钥是公开的。与基于单个密钥的对称方法相比,此模型提供了更高的安全性,对称方法需要在发送者和接收者之间共享密钥。GnuPG 是 RFC4880 定义的 OpenPGP 标准的免费软件实现。GnuPG 支持非对称和对称算法。有关更多信息,请访问 https://gnupg.org。
GPG本文大量使用 GPG 与存储在您的保险箱中的文件进行交互。许多教程和操作指南可以引导您完成如何正确设置和管理密钥的过程。强烈建议配置 gpg-agent,以避免每次与您的私钥交互时都必须键入密码。用于此任务的一种流行方法是 Keychain,因为它也能够管理 ssh-agent。
让我们以管理凭据的经典示例为例。这是一项必要的麻烦,虽然 pass 和 KeePassC 看起来都很有趣,但我还不相信它们会适合我的工作流程。此外,我绝对不会被任何“复制到剪贴板”功能所迷惑。你们都见过 IRC 等平台上不可避免的剪贴板泄漏——谢谢,不用了!目前,让我们通过在一个文件中管理这些数据,将这项工作整合到一个“保险箱”概念中。文件中的每一行都将符合以下简单格式
resource:userid:password
其中 “resource” 是容易记忆的东西,例如 FQDN,甚至是像路由器这样的硬件设备,它仅限于提供 telnet 访问。userid
和 password
字段都表示为提示。这种提示方法效果很好,因为我自觉地努力限制我经常使用的用户 ID 和密码的数量。这意味着只需要一个提示就可以启动肌肉记忆。如果某个特定资源使用一些特殊的复杂性规则,我可以快速理解细微的变化,并通过相应地修改提示。例如,提示 “fo” 可能最终变成 “!fo” 或 “fO”。当您需要使用特别长的密码时,就会出现实现安全性和可用性之间平衡的另一个示例。一种实用的解决方案是将熟悉的密码组合起来,并相应地记录提示。例如,表示 “fo” 和 “ba” 组合的提示可以表示为 “fo..ba”。最后,提示方法提供了合理的后备保护,因为有限的信息对入侵者来说几乎没有用处。
尽管很隐晦,但将这些数据明文存储是愚蠢且不负责任的。配置 GnuPG 后,就有了使用您的私钥加密数据的机会。创建文件后,我的工作流程看起来像这样
$ gpg --ear <my key id> <file>
$ shred -u <file>
更新文件将涉及解密、编辑和重复上述步骤。这在一段时间内是可以容忍的,因为实际上,我不是每天都在建立凭据。但是,我知道总有一天,这种乏味的工作流程会变得不堪重负。正如预期的那样,当我发现自己保留了与保险相关的笔记,然后考虑使用相同的技术对其进行加密时,那一天到来了。现在,我谈论的是管理多个文件——这清楚地表明是时候编写一个脚本来充当包装器了。我的要求很简单
-
利用常用工具,例如 GPG、shred 和 bash 内置命令。
-
减少常用操作(加密、解密等)的输入。
-
保持代码简洁易读,以便适应未来的扩展。
-
支持纯文本文件,但避免对它们进行微观管理。
有趣的是,vim-gnupg Vim 插件 可以轻松处理这些要求,因为它与以 .asc、.gpg 或 .pgp 扩展名结尾的文件无缝集成。尽管它具有这些能力,但我还是想避免管理多个加密文件,而是使用某种更高级别的“保险库”。考虑到这个目标,最初的脚手架被拼凑在一起
#!/bin/bash
CONF=${HOME}/.saferc
[ -f $CONF ] && . $CONF
[ -z "$SOURCE_DIR" ] && SOURCE_DIR=${HOME}/safe
SOURCE_BASE=$(basename $SOURCE_DIR)
TAR_ENC=$HOME/${SOURCE_BASE}.tar.gz.asc
TAR="tar -C $(dirname $SOURCE_DIR)"
usage() {
cat <
这个框架足够简单,可以从中构建,并建立了一些基本规则。首先,您将通过将文件维护在单个 tar 归档文件中来避免微观管理文件。除非在 ~/.saferc 中定义了 $SOURCE_DIR
变量,否则它将回退到 $HOME/safe。考虑到未来,这将允许人们协作处理此项目,而无需反复覆盖变量。无论如何,$SOURCE_DIR
的值用作 $SOURCE_BASE
、$TAR_ENC
和 $TAR
变量的基础。如果我的 ~/.saferc 将 $SOURCE_DIR
定义为 $HOME/foo,则我的保险箱将维护为 $HOME/foo.tar.gz.asc。如果我选择不维护 ~/.saferc 文件,则我的保险箱将位于 $HOME/safe.tar.gz.asc 中。
回到这个原始脚本,让我们将重点仅限于能够打开和关闭保险箱。让我们首先处理 create_safe()
函数,以便您稍后可以提取一些内容
create_safe() {
[ -d $SOURCE_DIR ] || { "Missing directory: $SOURCE_DIR"; exit 1; }
$TAR -cz $SOURCE_BASE | gpg -ear $(whoami) --yes -o $TAR_ENC
find $SOURCE_DIR -type f | xargs shred -u
rm -fr $SOURCE_DIR
}
create_safe()
函数目前看起来相当不错,因为它自动化了许多繁琐的步骤。首先,您确保归档文件的基本目录存在。如果存在,则将目录压缩到 tar 归档文件中,并将输出直接管道传输到 GPG,以便加密最终结果。请注意 whoami
的结果是如何用于 GPG 的 -r
选项的。这假设可以使用与登录到系统的 ID 相同的 ID 来引用私有 GPG 密钥。这纯粹是为了方便,因为我已经注意保持这些元素同步,但如果您的设置不同,则需要修改它。实际上,我可以看到最终支持使用 ~/.saferc 方法进行某种形式的覆盖。但现在,让我们把这个想法放在一边。最后,该函数对基本目录中的所有文件调用 shred 二进制文件。这通过自动化清理解决了恼人的“我是否有一个纯文本版本遗留下来?”的难题。
现在您应该能够创建保险箱了。假设不存在 ~/.saferc 并且 $PATH 环境变量包含包含 safe.sh 的目录,您可以开始测试此脚本
$ cd
$ mkdir safe
$ for i in $(seq 5); do echo "this is secret #$i" >
↪safe/file${i}.txt; done
$ safe.sh -c
现在您应该在您的主目录中有一个名为 safe.tar.gz.asc 的文件。这是一个加密的 tarball,其中包含先前写入 ~/safe 目录的五个文件。然后,您通过粉碎每个文件并最终删除 ~/safe 目录来清理了内容。现在可能是认识到您的设计是基于管理单个文件目录的期望的好时机。就我的目的而言,这是可以接受的。如果需要子目录,则需要相应地重构代码。
既然您能够创建您的保险箱,让我们专注于能够打开它。以下 extract_safe()
函数将很好地完成这项工作
extract_safe() {
[ -f $TAR_ENC ] || { "Missing file: $TAR_ENC"; exit 1; }
gpg --batch -q -d $TAR_ENC | $TAR -zx
}
本质上,您只是以相反的顺序使用 GPG 和 tar。通过使用 -x
运行脚本打开保险箱后,您应该看到 ~/safe 目录。
事情似乎进展顺利,但是您很容易看到需要列出您的保险箱的内容,因为您不想每次都打开它才能知道里面有什么。让我们添加一个 list_safe()
函数
list_safe() {
[ -f $TAR_ENC ] || { "Missing file: $TAR_ENC"; exit 1; }
gpg --batch -q -d $TAR_ENC | tar -zt
}
没什么大不了的,因为您只是使用 tar 的列出内容而不是提取内容的能力。当您在这里时,您可以开始通过将所有文件和目录测试整合到一个函数中来稍微 DRY 一下。您甚至可以添加一个方便的小备份功能,将您的归档文件 scp
到远程主机。清单 1 是到目前为止脚本的更新版本。
#!/bin/bash
#
# safe.sh - wrapper to interact with my encrypted file archive
CONF=${HOME}/.saferc
[ -f $CONF ] && . $CONF
[ -z "$SOURCE_DIR" ] && SOURCE_DIR=${HOME}/safe
SOURCE_BASE=$(basename $SOURCE_DIR)
TAR_ENC=$HOME/${SOURCE_BASE}.tar.gz.asc
TAR="tar -C $(dirname $SOURCE_DIR)"
usage() {
cat < /dev/null
[ $? -eq 0 ] && echo OK || echo Failed
done
新的 -b
选项需要将主机名作为参数传递。使用时,归档文件将相应地被 scp
。作为奖励,您可以多次使用 -b
选项以备份到多个主机。这意味着您可以选择配置例行的 cron 作业来自动化您的备份,同时仍然能够在任何时候运行“一次性”备份。当然,如果您计划自动化您的备份,您将需要管理您的 SSH 密钥并配置 ssh-agent。最近,我已经转换为 pam_ssh () 以启动我的 ssh-agent,但这又是另一个话题了。
回到代码,有一个小的 is_or_die()
函数,它接受一个参数,但回退到 $TAR_ENC
中指定的归档文件。这将有助于保持脚本简洁明了,因为根据使用的选项,您知道您将需要在采取行动之前检查一个或多个文件和/或目录。
在本文的剩余部分,我将避免完整地写出更新后的脚本。相反,我只在添加新功能时提供小的代码片段。
首先,如何添加输出存储在您的保险箱中的单个文件内容的功能?您需要做的就是检查文件的存在并适当地修改您的 tar 选项。实际上,您有机会通过简单地重构您的 extract_safe()
函数来避免重新发明轮子。更新后的函数将在单个文件上运行(如果相应地调用)。否则,它将在整个归档文件上运行。值得注意的是,为了提供一点用户友好性而采取的额外步骤。使用默认的 $SOURCE_DIR
~/safe,用户可以将 safe/my_file 或仅 my_file 传递给 -o 选项
list_safe() {
is_or_die
gpg --batch -q -d $TAR_ENC | tar -zt | sort
}
search_safe() {
is_or_die
FILE=${1#*/}
for f in $(list_safe); do
ARCHIVE_FILE=${f#$SOURCE_BASE/}
[ "$ARCHIVE_FILE" == "$FILE" ] && return
done
false
}
extract_safe() {
is_or_die
OPTS=" -zx"
[ $# -eq 1 ] && OPTS+=" $SOURCE_BASE/${1#*/} -O"
gpg --batch -q -d $TAR_ENC | $TAR $OPTS
}
safe.sh 的最终版本维护在 https://github.com/windowsrefund/safe。它支持更多用例,例如添加和删除文件的功能。在添加这些功能时,我试图避免实际将归档文件提取到磁盘作为修改其内容的前奏。由于 GNU tar 拒绝在使用 -r
时从 STDIN 读取,因此我没有成功。GnuPG 的 gpg-zip 二进制文件可能存在连接 GPG 和 tar 的管道的不错替代方案。但是,Arch 软件包维护者似乎只包含了 gpg-zip 手册页。简而言之,我更喜欢“尽可能保持简单;但不要更简单”的方法。如果有人有兴趣改进用于添加和删除文件的方法,请随时提交您的 pull request。这也适用于 edit_safe()
函数,尽管考虑到 vim-gnupg 插件的 最近的活动,我预见到在某个时候会重构它。
我选择的 MUA 是 mutt。像许多人一样,我已将我的邮件客户端配置为与多个 IMAP 帐户交互,每个帐户都需要身份验证。一般来说,这些凭据可以简单地硬编码在一个或多个配置文件中,但这会导致羞愧、后悔和可怕的事情。相反,让我们使用 Aaron Toponce 的巧妙方法 的一个稍微变体,该方法使 mutt 能够解密和获取敏感数据
$ echo "set my_pass_imap = l@mepassw0rd" > /tmp/pass_mail
$ safe.sh -a /tmp/pass_mail
现在您的保险箱包含 pass_mail 文件;您可以使用 ~/.muttrc 中的这一行让 mutt 读取它
source "safe.sh -o pass_mail |"
通过读取文件,mutt 初始化了一个您命名为 my_pass_imap
的变量。该变量可以在 mutt 配置的其他区域中使用。例如,您的 mutt 配置的另一个区域可以使用以下行
set imap_user = "my_user_id"
set imap_pass = $my_pass_imap
set folder = "imaps://example.com"
set smtp_url = smtp://$imap_user:$imap_pass@example.com
通过将适当命名的变量与 mutt 支持多个帐户的能力相结合,可以使用此技术安全地管理您的所有邮件相关凭据,而无需在您的硬盘驱动器上存储纯文本副本。