使用 OpenSSL 和 GPG 的平面文件加密

长期以来,良好隐私密码法 (PGP) 应用程序一直被认为是文件加密的主要工具,通常侧重于电子邮件。它具有用于与对等方交换凭据并在不受信任的网络上创建安全通信通道的管理工具。GNU 隐私卫士 (GPG) 以免费和开源的实现继承了这一传统,该实现包含在大多数主要的 Linux 发行版中。PGP/GPG 已被证明具有很强的抵抗密码学攻击能力,并且是安全通信的首要工具。

OpenSSL 更以为人所知的网络安全而闻名,但它也拥有对大多数平面文件加密方面有用的工具。虽然使用 OpenSSL 需要更多关于特定算法和方法的知识,但在许多情况下,它可能比其他方法更灵活。OpenSSH 密钥可以透明地用于 OpenSSL 的平面文件加密,允许用户和/或主机 SSH 密钥渗透到任意数量的不相关服务中。

OpenSSL 也可用于说明创建安全通道的加密技术序列。这些知识在许多其他情况下都适用,因此即使目前不需要这些工具,这些材料也值得研究。

OpenSSL 平面文件处理

UNIX 中的许多常用程序都在 OpenSSL 命令行实用程序中实现了。这些程序包括摘要/校验和工具(md5sum、sha1sum、sha256sum)、“ASCII-Armor”工具(base64/uuencode/uudecode)、“安全”随机数生成和 MIME 函数,以及一套密码和密钥管理实用程序。由于 OpenSSL 通常可以在非 UNIX 平台上找到,因此这些实用程序可以为 UNIX 管理员在不熟悉的系统上提供熟悉的界面。

让我们从一个完整的脚本开始,用于使用 OpenSSL 进行平面文件加密,使用会话密钥的非对称交换、SHA-256 摘要校验和以及对称密码的使用。以下文本中介绍了 Korn shell 的整个交换过程,包括编码和解码(GNU Bash 也可使用,无需更改)


#!/bin/ksh

set -euo pipefail
IFS=$'\n\t' #http://redsymbol.net/articles/unofficial-bash-strict-mode/

# openssl genrsa -aes256 -out ~/.prv.key 2868        # Make private key
# openssl rsa -in ~/.prv.key -pubout -out ~/.pub.key # Make public key

PVK=~/.prv.key; PBK=~/.pub.key
SKEY=$(mktemp -t crypter-session_key-XXXXXX)          # Symmetric key

case $(basename "${0}") in

encrypter) ####sender needs only public key - not .pas or .prv.key#####
 openssl rand -base64 48 -out "${SKEY}"              # Generate sesskey
 openssl rsautl -encrypt -pubin -inkey "${PBK}" -in "${SKEY}" |
  openssl base64;
 echo __:
 for F                                                # Generate digest
 do echo $(openssl dgst -sha256 "${F}" | sed 's/^[^ ]*[ ]//') "${F}"
 done | openssl enc -aes-128-cbc -salt -a -e -pass "file:${SKEY}"
 echo __:
 for F                                                # Encrypt files
 do openssl enc -aes-128-cbc -salt -a -e -pass "file:${SKEY}" -in "${F}"
    echo __:
 done ;;

decrypter) #####receiver###############################################
 TMP=$(mktemp -t crypter-tmp-XXXXXX); PW=${HOME}/.pas; unset IFS
 DGST=$(mktemp -t crypter-dgst-XXXXXX); #cd ${HOME}/dest #unpack dest
 while read Z
 do if [[ ${Z%%:*} == '__' ]]
    then if [[ -s "${SKEY}" ]]
         then if [[ -s "${DGST}" ]]
              then openssl enc -aes-128-cbc -d -salt -a -in "${TMP}" \
                    -pass "file:${SKEY}" -out "${NAME[I]}"
                   ((I+=1))                           # Decrypt files
              else openssl enc -aes-128-cbc -d -salt -a -in "${TMP}" \
                    -pass "file:${SKEY}" -out "${DGST}"
                   date +%Y/%m/%d:%H:%M:%S
                   I=0
                   while read hash file
                   do echo "${hash} ${file}"
                      HASH[I]=${hash}
                      NAME[I]=$(basename "${file}")  # Unpack only @dest
                      ((I+=1))
                   done < "${DGST}"
                   I=0
              fi
         else openssl base64 -d -in "${TMP}" |       # Extract sesskey
               openssl rsautl -decrypt -inkey "${PVK}" \
                -passin "file:${PW}" -out "${SKEY}"

             #Older OpenSSL: decrypt PVK; c/sha256/sha1/; no strict
             #openssl rsa -in "${PVK}" -passin "file:${PW}" -out "$DGST"
             #openssl base64 -d -in "${TMP}" |        # Extract sesskey
             # openssl rsautl -decrypt -inkey "${DGST}" -out "${SKEY}"
             #> "${DGST}"
         fi
         > "${TMP}"                                   # Erase tempfile
    else echo "${Z}" >> ${TMP}
    fi
 done
 I=0
 while [[ ${I} -lt ${#NAME[*]} ]]                     # Verify digest
 do F=$(openssl dgst -sha256 "${NAME[I]}" | sed 's/^[^ ]*[ ]//')
    if [[ "${F}" = "${HASH[I]}" ]]
    then echo "${NAME[I]}: ok"; else echo "${NAME[I]}: **SHA CORRUPT**"
    fi
    ((I+=1))
 done
 rm "${TMP}" "${DGST}" ;;

esac
rm "${SKEY}"

我将专门介绍以上所有内容,直到 encrypter case 代码块的末尾,因为这简洁地解决了大多数加密工具(即 SSH、TLS、PGP 等)的主要加密组件。

首先,我包含了一个由 Aaron Maxwell 发布的众所周知的 Korn/Bash 严格模式,它可以防止编码错误,如脚本顶部附近的 URL 中所述。

接下来,我生成一个 RSA 私钥。RSA 作为一种“非对称密码”,使用密钥对进行通信,由 Ron Rivest、Adi Shamir 和 Leonard Adleman 于 1977 年开发。常用的其他非对称密码包括 Diffie-Hellman 密钥交换和椭圆曲线,但 OpenSSL 对 RSA 的支持更全面、完整和广泛(OpenSSL 的 dhparam 手册页中列出的一个错误表明“应该有一种生成和操作 DH 密钥的方法。”)。使用非对称密码,用一个密钥加密的内容只能用另一个密钥以明文形式读取。您不仅可以使用此类密钥对进行安全通信,还可以证明真实性。以下是生成 2868 位非标准大小的 RSA 私钥的示例


$ openssl genrsa -aes256 -out ~/.prv.key 2868
Generating RSA private key, 2868 bit long modulus
.............++
....................................................................++
e is 65537 (0x10001)
Enter pass phrase for /home/ol7_user/.prv.key:
Verifying - Enter pass phrase for /home/ol7_user/.prv.key:

$ chmod 400 .prv.key

$ cat .prv.key
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,16846D1D37C82C834E65B518C456DE2F

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
-----END RSA PRIVATE KEY-----

我选择此非标准密钥大小是基于美国国家标准与技术研究院 (NIST) 的建议。NIST 在其实施指南文档的第 92 页上使用“通用数域筛选法”来确定 RSA 密钥的最小大小。您可以使用 GNU bc 实用程序(GNU Coreutils 的一部分)来实现此公式


$ cat keysize-NIST.bc
#!/usr/bin/bc -l
l = read()
scale = 14; a = 1/3; b = 2/3; t = l * l(2); m = l(t) # a^b == e(l(a) * b)
n = e( l(m) * b ); o = e( l(t) * a ); p = (1.923 * o * n - 4.69) / l(2)
print "Strength: ", p, "\n"

$ echo 2868 | ./keysize-NIST.bc
Strength: 128.01675571278223
$ echo 7295 | ./keysize-NIST.bc
Strength: 192.00346260354399
$ echo 14446 | ./keysize-NIST.bc
Strength: 256.00032964845911

$ echo 2048 | ./keysize-NIST.bc
Strength: 110.11760837749330
$ echo 2127 | ./keysize-NIST.bc
Strength: 112.01273358822347

一般来说,非对称密码比“对称密码”(定义为仅使用一个密钥进行加密和解密)更慢且更弱。稍后我将使用 128 位对称密码来传输我的大部分数据,因此我将使用(严格)相当强度的 2868 位 RSA 密钥。请注意,许多人认为超过 2048 位的 RSA 密钥大小是一种浪费。尽管如此,密码学领域最前沿的思想家推测,可能存在“...一些影响一种或多种公钥算法的数学突破。公钥密码分析中涉及许多数学技巧,并且绝对没有理论可以限制这些技巧的强大程度……。解决方法很简单:增加密钥长度。”在任何情况下,我在生成密钥时都严格遵循 NIST 的建议指南。

我列出了 192 位和 256 位等效项,因为这种对称密码未获批准用于 128 位的“绝密”用途。对于必须保密的极度敏感信息,请考虑使用 NIST 公式(严格)建议的 7295 或 14446 位 RSA 密钥大小。请注意,2048 位 RSA 密钥大小计算出的等效强度为 110 位。这低于 RFC-7525 对最低 112 位安全性的要求(建议 128 位)—2127 位 RSA 密钥满足此要求。

可以生成相应的公钥以与脚本一起使用


$ openssl rsa -in ~/.prv.key -pubout -out ~/.pub.key
Enter pass phrase for /home/ol7_user/.prv.key:
writing RSA key

$ cat .pub.key
-----BEGIN PUBLIC KEY-----
MIIBiDANBgkqhkiG9w0BAQEFAAOCAXUAMIIBcAKCAWcKpAcsnLXxoH4+ed2Bof2I
upOEwTYdz+N5R++7D/0Eo1LJKrq7CUq6D7jEjeBc/7Wr8mvvBVDgxi4eoYVpbaQa
NgTn1OSa7V7HH0DPVVjXfpIfF6qgk5R98L1Tyqz2agR3GF6F6QL+cxAscl0uFU2g
b/m66VHvxPVwi9ood20aPzBO6e01C6/l6l1tUMaS7PllQdFIXQe0i8ooAtEpvK5D
uBMebUjK0NjPsYxLSQJvJkNW1Sx2KBbIRKFEWPBZ0tFZ8PNokjez2LEV+CaX3ccc
tmeMvdg+w4PwuKmnWxCq0inFlDBE67aTMuYD8Wq7ATxtkkuc2aYL52jfD5YfTCkY
N41aH2w9ICTsuoVNfMUBJRtbhA0w7uoxkWnV2/a6N7VLCbeJncDaNABiOsn80MzY
bfJVrTHVqS0wPt3LY2Pt6/ZjQUejQwhKCjzgqx5DvzgGuTck3J0akhUvTe79OoCC
ZSeanYhX5QIDAQAB
-----END PUBLIC KEY-----

私钥与 OpenSSH 协议 2 RSA 格式兼容,您可以使用简单的 keygen 命令生成通常存储为 id_rsa.pub 文件的内容


$ ssh-keygen -y -f ~/.prv.key
Enter passphrase:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABZwqkByyctfGgfj553YGh/Yi6k4TBNh3P43lH7
7sP/QSjUskqursJSroPuMSN4Fz/tavya+8FUODGLh6hhWltpBo2BOfU5JrtXscfQM9VWNd+kh
8XqqCTlH3wvVPKrPZqBHcYXoXpAv5zECxyXS4VTaBv+brpUe/E9XCL2ih3bRo/ME7p7TULr+X
qXW1QxpLs+WVB0UhdB7SLyigC0Sm8rkO4Ex5tSMrQ2M+xjEtJAm8mQ1bVLHYoFshEoURY8FnS
0Vnw82iSN7PYsRX4Jpfdxxy2Z4y92D7Dg/C4qadbEKrSKcWUMETrtpMy5gPxarsBPG2SS5zZp
gvnaN8Plh9MKRg3jVofbD0gJOy6hU18xQElG1uEDTDu6jGRadXb9ro3tUsJt4mdwNo0AGI6yf
zQzNht8lWtMdWpLTA+3ctjY+3r9mNBR6NDCEoKPOCrHkO/OAa5NyTcnRqSFS9N7v06gIJlJ5q
diFfl

SSH 服务器也使用多种类型的主机密钥(通常不使用密码)运行,通常大小为 2048 位。主机的私有 RSA 密钥可以与我的 crypter 脚本一起使用,方法是使用此命令生成兼容的公钥


# openssl rsa -in /etc/ssh/ssh_host_rsa_key -pubout
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuxg2zI4ANHRCp+roqjnb
z/h6dc/ijs8uEwXnXE9mID02QvzusciQeeBUcRPXU5ncdPMzNuhUeiNK9y02vs9G
MzkV8vxciBGe6ovFERIDuE1QQPR3V1wZwsVjnG+65bxmGp5/OZpgE4WzMaMm3gla
iDnhfMUllUVzErNoMnR5yCQaoIW9j/AUiBtAymQ07YJcuVrxXBjzGWc/7ryHU1KH
IxKUJfwOhdgf81l0YNpoPdyImCV8PQdBIi8kTnuUl2hIPV2mOP3KWtINfOd94OLM
qfXd5F9LKkKW4XH55wfmJBsO6DTwhzGI9YOayGVJhdraOk7R84ZC/K4rt5ondgpO
3QIDAQAB
-----END PUBLIC KEY-----

由于我将使用此 RSA 密钥对进行批量传输,因此我将在 ~/.pas 文件中记录此密钥的明文密码。因此,RSA 密钥可能不应用于 SSH。OpenSSL 能够从各种其他来源读取密码,因此,如果您删除 ~/.pas 文件并从更安全的来源提供密码,则将单个 RSA 密钥用于 SSH 网络会话和 OpenSSL 平面文件加密变得更可行。或者,使用不带密码的密钥,并省去上面的 ${PW} 子句。

您不能使用 RSA 密钥对对大量数据进行批量加密,因为 RSA 只能编码少量信息,这在手册页中有详细说明


$ man rsautl | col -b | awk '/NOTES/,/^$/'
NOTES
  rsautl, because it uses the RSA algorithm directly, can only be used
  to sign or verify small pieces of data.

将一串零回显到一个文本文件,对于我的 2868 位 RSA 密钥,RSA 加密的明文输入的最大大小为 348 字节


$ for((x=0;x<348;x+=1));do echo -n 0 >> bar;done

$ ll bar
-rw-rw-r--. 1 ol7_user ol7_user 348 Jul  7 17:49 bar

$ openssl rsautl -encrypt -pubin -inkey ~/.pub.key -in bar |
        openssl base64
BCfCA77mmbaLCsMQVFCw/uMYWI0+4FaK6meFuTL2OXP6neGa0elrszbAePeoCA/x
dMykxgYBFa/uM2nJl9vagKOlU+DAlRojWGAjrCqfF9XNhdnOjsNINsgNTTzKlVxh
aLfEMYB+vyIwWdaKTrpTz/v7wB20wL9l7eewLZh9yNy4tzyE83Tt5zsgWCvxIdLN
cqkZw7aHvXuXMzdNZn0PoQV/VKLvlmJU5IpDxUCcfPnvZd//f5Akb0tKO44x9hpz
jp/DhRqOYEaB67k5U8GZWYPZoy0XCfLAtSaLMnAkw6swqikVm1IDmLzsRsURgyGX
Qafbh4F33ivn7jaRNbSKbFmSMYc1ShACJuTgTQ2N519gc84Sd1TvSyL7v+m5WqXF
fuPJiIrpi6DkYZDOuNQP0cjEMVHLVuwjFh98uW7IyJY5sGVP+/cVlmVg9SUDhpNt
t6naZ/CwkyHal6PaFa4AhlDGNJ/RVNc=

$ echo -n 0 >> bar

$ ll bar
-rw-rw-r--. 1 ol7_user ol7_user 349 Jul  7 17:49 bar

$ openssl rsautl -encrypt -pubin -inkey ~/.pub.key -in bar |
        openssl base64
RSA operation error
139936549824416:error:0406D06E:rsa routines:
RSA_padding_add_PKCS1_type_2:data too large for key size:
rsa_pk1.c:151:

使用 2048 位 RSA 密钥进行的类似测试,最大值为 245 字节(略小于密钥大小)。

由于此限制,我将使用 OpenSSL 的随机数生成器生成一个随机的 64 字符密码,用公钥加密其副本并将其放入输出中,然后将其用作会话密钥,用于数据的对称加密。对于此示例,我将使用以下通过这种方式从 OpenSSL 获得的密码


$ openssl rand -base64 48
d25/H928tZ1BaXzJ+jRg/3CmLYxaM5kCPkOvkIxKAoIE8ajiwu+0zWz0SpDXJ5J7

如果我将此文件存储为 /tmp/skey,我可以看到加密发生


$ openssl rsautl -encrypt -pubin -inkey ~/.pub.key -in /tmp/skey |
        openssl base64
Ac5XfYjJUpJGRiCNVSPcRi7SBrEVBtQhVHgqYWgQH6eFrDuQLX4s/S50qKt1ObjT
17aV8pDMGqiHXOsbfD/P/GBpiymgQUJoa4VS40J+d5u9X20NmxmtNAvvlklmCC9q
lzJcX6QXg4QEDTOHD+jU0B3K5QOB3von0IIVgauKGfDvgkOJiqjK9bUhhSgdnNe3
yyivWXb8Xl+zDCSqtqtv0Xkzri2jmTXniu7HztGTnyOcpZ4PLFMT9ZC0Biu40xK9
ubuMPcfpVKVKRuR0iAu1kkstQY2k6xieZiIDIMtg4vHJIdb793aC8Spuhjca1puS
QaQTfkQIrN46oJ6IoGqmTMGem6IGiUAldan24nTl7C+Z7aF1nieXb55gDwfQcO55
Uk/1tbgQR6MMzXG6BglmjD6oa/urKjI2taJT02c+IT6w6nXpGWrGBMY5S7G8u++Y
tml7ILPwiA4lKhvukgbPZw/vFgNAGxo=

请注意上面对 base64 函数的调用 — 加密输出是一个二进制文件,不能使用标准的 ASCII 七位字符集直接显示。base64 编码器将二进制数据更改为适合在七位通道(例如,电子邮件)上传输的可打印字符流。这执行与 uuencode 相同的功能,但使用不同的 ASCII 符号。

如果我将加密的输出记录在 /tmp/ekey 文件中,我可以使用私钥对其进行解密


$ openssl base64 -d < /tmp/ekey |
        openssl rsautl -decrypt -inkey ~/.prv.key
Enter pass phrase for .prv.key:
d25/H928tZ1BaXzJ+jRg/3CmLYxaM5kCPkOvkIxKAoIE8ajiwu+0zWz0SpDXJ5J7

请注意上面的解密部分,旧版本的 OpenSSL rsautl 命令不允许在命令行上指定密码。因此,必须先创建一个未加密的密钥副本,然后才能对会话密钥进行 RSA 解密。该过程记录在旧版系统和版本的注释中。

获得会话密钥后,我接下来计算所有输入文件的 SHA-256 摘要校验和,并将加密结果记录在输出中。OpenSSL 版本的 sha256sum 实用程序的格式与传统版本略有不同。下面还包括 SHA-1、RIPEMD-160 和 MD5 校验和


$ sha256sum /etc/resolv.conf
04655aaa80ee78632d616c1...4bd61c70b7550eacd5d10e8961a70  /etc/resolv.conf

$ openssl dgst -sha256 /etc/resolv.conf
SHA256(/etc/resolv.conf)= 04655aaa80ee78632d6...1c70b7550eacd5d10e8961a70


$ openssl dgst -sha1 /etc/resolv.conf
SHA1(/etc/resolv.conf)= adffc1b0f9620b6709e299299d2ea98414adca2c
$ openssl dgst -ripemd160 /etc/resolv.conf
RIPEMD160(/etc/resolv.conf)= 9929f6385e3260e52ba8ef58a0000ad1261f4f31
$ openssl dgst -md5 /etc/resolv.conf
MD5(/etc/resolv.conf)= 6ce7764fb66a70f6414e9f56a7e1d15b

SHA 系列摘要均由 NSA 创建,我们非常感谢他们的发布。RIPEMD-160 摘要由比利时的研究人员开发,是 SHA 的开放替代方案,没有已知的缺陷,但它比 SHA-1 慢,并且发布时间较晚,因此使用频率不高。MD5 摘要不应超出基本媒体错误检测范围使用,因为它们容易受到篡改

该脚本调整了 OpenSSL 生成的格式,使其更接近标准实用程序,然后在打印分隔符 (__:) 后,使用 AES128-CBC 对称密码对所有输入文件的摘要进行编码。非常旧版本的 OpenSSL 实用程序可能缺少 SHA-256 — 脚本中的注释详细说明了在使用旧版系统时降级为较弱的 SHA-1(永远不应使用 MD5)。如果手册页可用,man dgst 命令将提供有关 OpenSSL 摘要选项的完整详细信息。

最后,脚本进入主加密循环,其中每个文件都使用 AES128-CBC 处理,使用 base64 编码,用分隔符分隔,然后发送到 STDOUT,目的是将脚本重定向/管道传输到文件或程序以进行进一步处理。有关 OpenSSL 的各种对称密码的信息,可以在手册页可访问安装时通过 man enc 命令找到。一个信息丰富且有趣的卡通已在网上发布,涵盖了 AES 的历史和操作理论,供那些对我们选择的对称密码有更深入兴趣的人参考。GPG 网站目前除了 AES 之外,还提倡 Camellia 和 Twofish,并且可以在 OpenSSL 中找到 Camellia。

可以调用 OpenSSL 以使用 AES 将文件加密到标准输出,如下所示


openssl enc -aes-128-cbc -salt -a -e -pass file:pw.txt
 ↪-in file.txt > file.aes

加密的撤消方式如下


openssl enc -aes-128-cbc -d -salt -a -pass file:pw.txt -in file.aes

这是一个脚本完整运行的示例


$ ln -s crypter.sh encrypter
$ ln -s crypter.sh decrypter
$ chmod 755 crypter.sh

$ ./encrypter /etc/resolv.conf /etc/hostname > foo

$ ./decrypter < foo
2016/07/05:21:24:38
04655aaa80ee78632d616c1...4bd61c70b7550eacd5d10e8961a70 /etc/resolv.conf
4796631793e89e4d6b5b203...37a4168b139ecdaee6a4a55b03468 /etc/hostname
resolv.conf: ok
hostname: ok

要使用此脚本,或以其他方式使用 OpenSSL 实用程序进行安全通信,只需将公钥发送给远端方即可。假设发送者和接收者之间验证了公钥的完整性(即,通过电话或其他受信任的通道上的 SHA-256 校验和),则发送者可以创建会话密钥,然后使用它来编码并通过任何不受信任但可靠的传输介质发送任意数量的数据,并具有合理的保密性信心。

请注意,解密代码块使用 shell 数组,在某些版本 (ksh88, pdksh) 中限制为 1024 个元素。在这些情况下,这将是硬性文件限制。

整个脚本可以融入电子邮件系统以进行自动传输。要在使用默认 Postfix SMTP 服务器的 Oracle Linux 7 上执行此操作,请确保在 /etc/postfix/main.cf 中设置以下两行


inet_interfaces = $myhostname, localhost
...
default_privs = nobody

在这里,我将 SSH 私有 RSA 主机密钥的副本放在 /etc/postfix 目录中,设置配置和权限,打开防火墙端口 25,然后生成如下所示的公钥


cd /etc/postfix
cp /etc/ssh/ssh_host_rsa_key .prv.key
chown nobody:nobody .prv.key
chmod 400 .prv.key
chcon system_u:object_r:postfix_etc_t:s0 .prv.key
iptables -I INPUT -p tcp --dport 25 --syn -j ACCEPT
openssl rsa -in .prv.key -pubout -out .pub.key

请注意,我正在使用 nobody 用户和系统主机密钥。如果您对此安全性感到不舒服,请注意密钥文件位于 ssh_keys 组中,并为 postfix 创建一个单独的用户来处理密钥对。

接下来,将 decrypter 的副本放在 /etc/postfix 中。必须修改脚本以执行以下操作:1) 跳过电子邮件标头,2) 从主机密钥处理中删除密码子句,3) 将 /tmp 设置为解压缩目录,以及 4) 为密钥对定义新位置。下面,sed 与就地编辑一起使用以完成此操作


sed -i.old '/^ while read Z/s:^:sed '"'"'1,/^$/d'"'"' |:
        s/^[ ]*-passin "[^"]*"//
        /^ DGST=/s:#.*$:cd /tmp:
        /^PVK=/c \
PVK=/etc/postfix/.prv.key; PBK=/etc/postfix/.pub.key' decrypter

完成这些更改后,我创建一个将触发 decrypter 的电子邮件别名


echo 'crypter: "| /etc/postfix/decrypter >> /tmp/cryp.log 2>&1"' \
        >> /etc/aliases
newaliases
chcon system_u:object_r:postfix_local_exec_t:s0 decrypter
postfix reload
systemctl restart postfix.service

现在,将 encrypter 输出通过管道传输到邮件客户端


cd /etc
encrypter resolv.conf hostname | mail crypter@localhost

发送到邮件客户端的文件应出现在 /tmp 中。将公钥移动到远程服务器,即可建立通过 SMTP 的自动加密文件传输。

也可以反向使用 RSA 加密,使用公钥解密。这在建立数据真实性方面很有用 — 例如,使用私钥加密少量明文(受 RSA 长度限制)


echo 'I have control of the private key.' |
  openssl rsautl -sign -inkey ~/.prv.key -passin "file:$HOME/.pas" |
  openssl base64 > blob

然后可以将 blob 文件发布在公共介质(网站、文件服务器等)中,并且公钥的持有者可以成功解密消息,如下所示


openssl base64 -d < blob |
        openssl rsautl -inkey ~/.pub.key -pubin

通过这样做,用户可以验证私钥是否参与了消息的创建,从而为已传输的数据提供了一定的真实性。公钥不被认为是秘密的,因此这建立了数据真实性,而不是数据隐私。

您可以从 SHA-256 签名程序调用中管道输入文本,而不是任意文本,从而以证明真实性的方式“签名”更大的文件


openssl dgst -sha256 crypter.sh |
  openssl rsautl -sign -inkey ~/.prv.key -passin "file:$HOME/.pas" |
  openssl base64 > csign

您以与之前完全相同的方式解密此文本,生成可以独立验证的 SHA-256 明文摘要。但是,OpenSSL 可以一步总结签名的 SHA-256 校验和(请注意,也可以操作完整的 x.509 密钥来签名摘要)


openssl dgst -sha256 -sign ~/.prv.key \
        -out crypter.sha256 crypter.sh

如果将以上两个文件放置在可访问的位置,则公钥的持有者可以验证文件是否已被更改


openssl dgst -sha256 -verify ~/.pub.key \
        -signature crypter.sha256 crypter.sh

当文件完好无损时,OpenSSL 应输出“Verified OK”。使用加密的 SHA-256 摘要安全地验证文件的功能远远超出了标准 sha256sum 实用程序的功能,并明确地证明了真实性。

GPG 简介

GNU 隐私卫士具有更全面的工具,用于管理密钥对和对等身份。这包括用于存储各种类型密钥的数据库、用于撤销密钥的工具以及用于在“信任网络”中建立密钥声誉的机制。

Oracle Linux 7 捆绑了 GPG 2.0.22,默认情况下使用 128 位 CAST5 对称密码(较新版本已切换到 AES128)。在这里,我将遵循之前的 NIST 指南,使用强度相等的 2868 位非对称密钥对(请注意,GPG 文档确实警告说,“超过 RSA-2048 意味着您将失去将证书迁移到智能卡或在某些移动设备上有效使用它,或与其他不优雅地处理大型密钥的 OpenPGP 应用程序互操作的能力。”)


$ gpg --gen-key

gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: directory `/home/ol7_user/.gnupg' created
gpg: new configuration file `/home/ol7_user/.gnupg/gpg.conf' created
gpg: WARNING: options in `/home/ol7_user/.gnupg/gpg.conf' are not yet
        active during this run
gpg: keyring `/home/ol7_user/.gnupg/secring.gpg' created
gpg: keyring `/home/ol7_user/.gnupg/pubring.gpg' created
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 2868
Requested keysize is 2868 bits
rounded up to 2880 bits
Please specify how long the key should be valid.
         0 = key does not expire
        = key expires in n days
      w = key expires in n weeks
      m = key expires in n months
      y = key expires in n years
Key is valid for? (0) 5y
Key expires at Sat 10 Jul 2021 08:40:19 PM CDT
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Oracle Linux
Email address: ol7_user@localhost
Comment: Test Key
You selected this USER-ID:
    "Oracle Linux (Test Key) <ol7_user@localhost>

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /home/ol7_user/.gnupg/trustdb.gpg: trustdb created
gpg: key 6F862596 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2021-07-11
pub   2880R/6F862596 2016-07-12 [expires: 2021-07-11]
      Key fingerprint = F423 3B2C ACE1 AD0E 95C3  4769 679D 66ED 6F86 2596
uid                  Oracle Linux (Test Key)
sub   2880R/FF79FC31 2016-07-12 [expires: 2021-07-11]

创建(向上取整)2880 位私钥后,需要一个命令来生成可以与他人共享的公钥


$ gpg --export -a

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)

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
=gBp9
-----END PGP PUBLIC KEY BLOCK-----

此密钥通常会包含在电子邮件消息末尾的签名中,与需要隐私或真实性的软件或文档捆绑在一起,或粘贴在公共论坛(例如网站)中,在这些论坛中可能会发生类似的活动。

希望与此密钥的发起者通信的其他 GPG/PGP 用户可能会先导入它,然后再进行这些活动


$ gpg --import /tmp/test.pub
gpg: key F3BD3FF5:
public key "Test User (Test Key) <testuser@localhost>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)
$ gpg --import /tmp/ol7.pub
gpg: key 6F862596:
public key "Oracle Linux (Test Key) <ol7_user@localhost>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)

有许多电子邮件软件包将直接使用各种 PGP 组件,从而实现集成加密。我的重点是平面文件加密,因此我将我的 GPG 演示限制在此特定操作中,并使用它来加密上一节中的脚本,从 ol7_user@localhost 发送到 testuser@localhost


$ gpg -u ol7_user@localhost -r testuser@localhost --armor
 ↪--sign --encrypt crypter.sh

You need a passphrase to unlock the secret key for
user: "Oracle Linux (Test Key) "
2880-bit RSA key, ID 6F862596, created 2016-07-12

$ mv crypter.sh.asc /tmp

$ head -5 /tmp/crypter.sh.asc
-----BEGIN PGP MESSAGE-----
Version: GnuPG v2.0.22 (GNU/Linux)

hQF0A05zbjK/t9mRAQs/fog4FSkocxnJBKp1hb64yGf1xiecqLWwZBqct3kLiU5e
Ekmqdt06E+XU4N3bMtt808SwSXSLvKWT18Iy6WtGz4r+B3dYAlHo1vfeSt3L5dE0

然后,收件人 (testuser) 可以登录并解密(默认情况下将转到标准输出)


gpg -d /tmp/crypter.sh.asc

任何导致 GPG 请求密钥密码的活动都会生成一个“代理”,该代理将绑定未来的 GPG 会话并提供凭据,因此无需重复输入密钥密码


testuser 4252 0:00 gpg-agent --daemon --use-standard-socket

GPG 私钥的持有者还可以以类似于 OpenSSL 的方式(但更灵活)对文件进行数字签名。添加签名有三种方法:创建一个压缩二进制文件,其中包含原始消息的打包副本;添加一个明文“ASCII-armored”签名,允许读取原始内容;或将二进制签名写入单独的文件(需要干净的文件和签名才能验证)。第一种方法将压缩二进制文件写入扩展名为 .gpg 的新文件


gpg -s crypter.sh
(or)
gpg --sign crypter.sh

第二种方法将添加一个明文签名,允许原始内容保持可见,并添加到扩展名为 .asc 的新文件中


gpg --clearsign crypter.sh

第三种方法会将二进制签名写入扩展名为 .sig 的单独文件中


gpg -b crypter.sh
(or)
gpg --detach-sign crypter.sh

所有这些方法都可以由公钥的持有者使用 gpg -v (file) 命令进行验证,其中 (file) 指向 GPG 的输出。

“虽然 GPG 能够支持多种类型的摘要和密码,但强制使用特定算法可能会导致与各种发行版和 PGP 软件版本的用户出现兼容性问题。”明智的做法是坚持通用版本的功能,而不是直接指定算法(此讨论可以在 man gpg 页面中找到)


man gpg | col -b | awk '/^INTEROPERABILITY/,/reduce/'

INTEROPERABILITY
  GnuPG tries to be a very flexible implementation of the OpenPGP
  standard. In particular, GnuPG implements many of the optional
  parts of the standard, such as the SHA-512 hash, and the ZLIB and
  BZIP2 compression algorithms. It is important to be aware that
  not all OpenPGP programs implement these optional algorithms and
  that by forcing their use via the --cipher-algo, --digest-algo,
  --cert-digest-algo, or --compress-algo options in GnuPG, it is
  possible to create a perfectly valid OpenPGP message, but one
  that cannot be read by the intended recipient.

  There are dozens of variations of OpenPGP programs available, and
  each supports a slightly different subset of these optional
  algorithms. For example, until recently, no (unhacked) version
  of PGP supported the BLOWFISH cipher algorithm. A message using
  BLOWFISH simply could not be read by a PGP user. By default, GnuPG
  uses the standard OpenPGP preferences system that will always do the
  right thing and create messages that are usable by all recipients,
  regardless of which OpenPGP program they use. Only override this safe
  default if you really know what you are doing.

  If you absolutely must override the safe default, or if the
  preferences on a given key are invalid for some reason, you are far
  better off using the --pgp6, --pgp7, or --pgp8 options. These
  options are safe as they do not force any particular algorithms in
  violation of OpenPGP, but rather reduce the available algorithms
  to a "PGP-safe" list.

GPG 还能够与 --batch 和各种 --passphrase 选项进行非交互式使用。对交互式和批处理活动使用相同的密钥可能是不明智的 — 在线通信使用电子邮件密钥,自动活动使用批处理密钥。GPG 提供了几个密钥撤销选项 — 准备好将它们用于任何泄露的密钥,尤其是自动密钥。

结论

对于平面文件使用,OpenSSL 可能比 TLS(甚至 SSH)等网络服务更可取,原因有以下几个

  • 删除 TLS 大大减少了服务器的攻击面。

  • 当加密过程离线发生且在网络上不可见时,可以消除或大大减少几类漏洞的范围:定时攻击(例如 Lucky Thirteen)、其他侧信道攻击(例如 CRIME)和版本控制攻击(例如 DROWN)。

  • OpenSSH 中使用了 OpenSSL 中的密码算法代码,这证明了其质量。OpenSSH 的审查非常彻底,安全记录也非常好。

  • OpenSSL 的 aes_core.c 作者之一是 Vincent Rijmen,他与密码学家 Joan Daemen 一起开发了 AES(尽管在可用的架构上替换了自定义高速汇编代码)。aes_core.c 代码的片段也出现在 libtomcrypt 库中,该库直接在 dropbear SSH 服务器中使用,我在之前的文章中讨论过(请参阅 2015 年 3 月刊中的“使用 systemd 的无限 BusyBox”)。

  • OpenSSL 对异构系统的支持为网络引入了比基本数学更多的有问题的代码。

  • 与网络功能相比,用于 OpenSSL 基本密码算法的代码审查花费了更多时间。仅仅是对源代码进行法律分析,以解决专利侵权问题,就可能使网络安全审查相形见绌(例如,Red Hat 最近关于 OpenSSL 中椭圆曲线的决策以及 Sun 为避免现有专利而对所述例程进行的仔细编码)。DTLS 心跳 TCP 实现不太可能受到类似的分析,并且它成为 OpenSSL 中发现的最大缺陷(从未影响平面文件处理)。

  • 脚本化解决方案可以更轻松地与自定义程序(新的压缩工具、备用数据源、旧版系统和应用程序等)接口。

使用此处提供的 crypter 脚本有一些缺点

  • 该脚本在每个文件的内容之间放置分隔符。任何观察流量的人都会知道发送的文件数量及其长度。如果这很麻烦,请使用 ZIP 实用程序仅发送一个文件 — 某些 ZIP 实用程序直接使用 AES,允许 RSA 交换 ZIP 存档的密码,然后在未加密的通道上传输 ZIP(即使文件内容保持不透明,这也可能允许观察者读取 ZIP 文件目录)。

  • 该脚本将读取每个文件两次 — 一次用于摘要,一次用于对称算法。这将花费时间、处理能力和 I/O(GPG 在一个步骤中完成所有这些操作)。

GPG 也有一些问题

  • 某些 PGP 实现可能在处理较大的 RSA 密钥时遇到问题。

  • PGP 实现之间的兼容性问题极大地影响了选择的摘要和密码。

  • GPG 2.0.22(在 Oracle Linux 7 中找到的旧版本)使用 SHA-1 摘要,该摘要已被弃用。

这些工具都不是完美的,但它们是安全通信的基石。思考它们对商业和可信通信的影响规模几乎是难以理解的。这些算法与它们普遍存在一样,通常也是不为人所知的。

希望本教程能对它们有所启发。

Charles Fisher 拥有爱荷华大学的电气工程学位,并在一家财富 500 强矿业和制造公司担任系统和数据库管理员。

加载 Disqus 评论