更强大的 SSH 密钥加密

信息安全 加密 SSH openssl
2021-08-15 01:05:56

我将服务器配置为使用 500,000 轮 SHA-512 加密用户密码。

问题是,如果使用相同(或相似长度)的密码/密码短语,标准 AES-128-CBC 加密的 SSH 私钥如何叠加?当然,这必须是一个人类可输入的密码短语,而缺乏熵是(希望)这里最薄弱的环节。我的理解是,无论密码短语有多弱,密钥强化都会扩大暴力破解密码短语所需的努力。

我很清楚,由于公钥是公开的,并且可用于验证私钥,因此该私钥的安全性将取决于密码(RSA 密钥的长度不会影响它的难易程度)揭示它)。我想检查会很快,所以我理想情况下希望增加轮数并使用更强大的密码套件,以便减缓密码短语的暴力破解过程。

使用 PKCS#8作为 SSH 私钥可以获得多少额外的密码安全性

我也想知道如何改进这一点。有没有办法进行这种加密

openssl pkcs8 -topk8 -v2 des3

使用比默认更多的回合(并且仍然被接受ssh)?另外,还有更强大的套件可以使用吗?我现在在这里处理 Centos 6.4(因为我喜欢 kickstart 脚本),所以如果我能提供帮助,最好不要弄乱安全程序套件,但也许存在比它更强大的对称密码套件PKCS#8 可以用吗?

我注意到的一件事是这里的 PBKDF2 似乎没有指定使用的底层哈希。查看列表,它似乎没有比 SHA1 更好。

我想找到一种方法来充分利用成功身份验证的可容忍约 0.5 秒,以帮助最大限度地提高暴力破解所需的计算量。

我想如果我真的关心加强我应该看看scrypt,但工具中没有对它的本地支持,所以这不能用于日常的 SSH 私钥管理(但它可能适合用于特殊应用)。

编辑:有趣。我在 CentOS 上的加密私钥如下所示:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,D3A046CD...

我想这不一定比 AES-128-CBC(这是我的 Mac 生产的)差。

2个回答

私钥存储发生的事情有点复杂,因为它涉及多年来积累的几层未指定的crud,并为向后兼容而保留。让我们揭开谜底。

对于其加密操作,包括私钥存储(我们目前感兴趣的),OpenSSH依赖于OpenSSL 库所以 OpenSSH 将支持 OpenSSL 所支持的。


私钥是数学对象的一串可以在其是,通常情况下,二值的结构进行编码(即的一串字节,而不是可打印的字符)。让我们假设一个 RSA 密钥。RSA 私钥的格式在PKCS#1 中定义ASN.1 结构,该结构将使用 DER 编码规则进行编码。

由于许多与加密货币相关的工具在 1990 年代初和中期开始出现,当时电子邮件最流行(网络还很年轻),工具努力使用可以粘贴到电子邮件中的字符(附件这些天文件还不常见)。值得注意的是,有一个早期的标准称为增强隐私的电子邮件,或“PEM”。该标准从未真正部署或使用过,其他系统胜过它(即PGPS/MIME),但 PEM 的一个特性仍然存在:一种将二进制对象编码为可打印文本的方法。这是PEM 格式它看起来像这样:

-----BEGIN SOMETHING-----
Some-optional: headers

Base64+Encoded+Data==
-----END SOMETHING-----

所以 PEM 是一种包装器,二进制数据以Base64编码,并添加了页眉和页脚行,其中包括一个类型(“SOMETHING”)。“可选标头”是后来添加的 OpenSSL,它从未被标准化,因此 PEM-with-headers 仅记录为“OpenSSL 的功能”。OpenSSL 文档就是这样,这意味着,为了知道这个过程究竟需要什么,你必须深入研究可怕的 OpenSSL 源代码。

这是一个未加密的 RSA 私钥,采用 PEM 格式:

-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDQ33ndDr5N/AI8y2PzrqGbadLeS5fSf2GsVJx2B2KxhazL2z5O
ufin+wjJ1hW12/zWyQs/9CFYQFrife+PrMUOdLitsmlD3l4lBQ29+XKsmPabtINP
JQ0n4dxgBGeFxTCd4lJwiysmVsXPnNrgQTcx2nirrIk1C7wSW9Ai9W3fZQIDAQAB
AoGBAKiKSvkW9nRSzzNjIwn0da7EG0UIVj+iTZwSwhVzLC32oVH1XTeFVKGnLJZA
y0/tbP2bSBqY0Xc2pp9v4yhZzr6/BUPX+N1FOW8Q5OXHMD4fXSixrX0vYOT8hQuC
ehTAXsStjkZqzCdCsKV9YIduTHoyjL2jG6QBvFQK7kHaYUwZAkEA+rp2b+eBDJrg
lqcPOE2HkCkQcReSW0OIoUgd2tIiPFL8HSNwKvvAAH+QBKL6jvecLswJneecon8Z
jsgn4K/EpwJBANVDultbYq/h3F5FbAQ4r6cMQ2ZmmhMFdt8rRvAdEz18CuobGvAQ
y31hU/InW0n+Z0oHCsIgyowSeCGwRLMJYRMCQGKDXQG+/k+Lku7emPZQUBFucQ1e
a5z8PfTQtxpBMj5thK2WPP5GiDwp4tZPiw8dbvpcJPMsC7k1Iz+cmT6JEUUCQBxz
X54mb+D06bgt3L4nbc+ERE2Z7H4TIYueM2V/C30NWktm+E4Ef5EnddJ9S6Fwbgkj
LV0+kKblI9+iq1eTLb8CQQC+QDF7Y1o4IpDGcu+3WhS/pI/CkXD2pDMJM6rGBgG6
g9D1VTPCx0LZAWK4GdmELhPM+0ePH4P24/VsJY4mvutQ
-----END RSA PRIVATE KEY-----

如您所见,类型是“RSA PRIVATE KEY”。可以通过以下方式探索 ASN.1 结构openssl asn1parse

$ openssl asn1parse -i -in keyraw.pem
    0:d=0  hl=4 l= 605 cons: SEQUENCE       
    4:d=1  hl=2 l=   1 prim:  INTEGER           :00
    7:d=1  hl=3 l= 129 prim:  INTEGER           :D0DF79DD0EBE4DFC023CCB63F3AEA19B69D2DE4B97D27F61AC549C760762B185ACCBDB3E4EB9F8A7FB08C9D615B5DBFCD6C90B3FF42158405AE27DEF8FACC50E74B8ADB26943DE5E25050DBDF972AC98F69BB4834F250D27E1DC60046785C5309DE252708B2B2656C5CF9CDAE0413731DA78ABAC89350BBC125BD022F56DDF65
  139:d=1  hl=2 l=   3 prim:  INTEGER           :010001
  144:d=1  hl=3 l= 129 prim:  INTEGER           :A88A4AF916F67452CF33632309F475AEC41B4508563FA24D9C12C215732C2DF6A151F55D378554A1A72C9640CB4FED6CFD9B481A98D17736A69F6FE32859CEBEBF0543D7F8DD45396F10E4E5C7303E1F5D28B1AD7D2F60E4FC850B827A14C05EC4AD8E466ACC2742B0A57D60876E4C7A328CBDA31BA401BC540AEE41DA614C19
  276:d=1  hl=2 l=  65 prim:  INTEGER           :FABA766FE7810C9AE096A70F384D879029107117925B4388A1481DDAD2223C52FC1D23702AFBC0007F9004A2FA8EF79C2ECC099DE79CA27F198EC827E0AFC4A7
  343:d=1  hl=2 l=  65 prim:  INTEGER           :D543BA5B5B62AFE1DC5E456C0438AFA70C4366669A130576DF2B46F01D133D7C0AEA1B1AF010CB7D6153F2275B49FE674A070AC220CA8C127821B044B3096113
  410:d=1  hl=2 l=  64 prim:  INTEGER           :62835D01BEFE4F8B92EEDE98F65050116E710D5E6B9CFC3DF4D0B71A41323E6D84AD963CFE46883C29E2D64F8B0F1D6EFA5C24F32C0BB935233F9C993E891145
  476:d=1  hl=2 l=  64 prim:  INTEGER           :1C735F9E266FE0F4E9B82DDCBE276DCF84444D99EC7E13218B9E33657F0B7D0D5A4B66F84E047F912775D27D4BA1706E09232D5D3E90A6E523DFA2AB57932DBF
  542:d=1  hl=2 l=  65 prim:  INTEGER           :BE40317B635A382290C672EFB75A14BFA48FC29170F6A4330933AAC60601BA83D0F55533C2C742D90162B819D9842E13CCFB478F1F83F6E3F56C258E26BEEB50

我们在这里认识到 RSA 私钥的组成部分:一些大整数。有关数学详细信息,请参见 PKCS#1。

碰巧 OpenSSL 使用的 PEM 扩展格式支持基于密码的加密经过一些代码阅读,事实证明加密使用的是 CBC 模式,在 headers 中指定了 IV 和算法;并且密码到密钥的转换依赖于EVP_BytesToKey()(定义在crypto\evp\evp_key.c)具有以下特性:

  • 这是一个非标准的基于散列的密钥派生函数
  • 用于加密的 IV 也用作盐。
  • 哈希函数是 MD5。
  • 哈希重复使用n次迭代,但在 PEM 加密的情况下,迭代计数n设置为 1。

KDF 是非标准的令人担忧。重用的盐加密IV是未成年人的担心(这是数学洁净,但可能不是一个真正的问题-而且,至少,还有就是盐)。使用 MD5 也是一个小问题(虽然 MD5 在碰撞方面被彻底破坏,但密钥派生通常依赖于原像抗性,MD5 仍然相当强大,几乎和新的一样好)。迭代计数设置为 1(这意味着根本没有循环)是一个严重的问题。

这意味着如果攻击者试图猜测 PEM 加密密钥的密码,每次尝试的计算成本将是最小的。使用好的 GPU,攻击者每秒可以尝试数十亿次密码这对舒适来说太快了。基于密码的密钥派生应该是saltedslow,OpenSSL PEM 加密格式在第二点上失败。有关详细讨论,请参阅此答案

这是一个 PEM 加密的私钥;加密算法设置为 AES-128。密码是“1234”:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,8680A1BEAE5661AAD8DA344B7495BCD4

4cvmuk8onrB5IQVRr6xRUBt6yRcjNUGcUWq0CcyX4p4iijANv/S7H5Ga8e5e+12m
k6UUt65mF54Ddh+WE4lHHy5yYEPa25tr/KBMErEhHJxYFiwRwgw/KoF2V8Cpgidd
BA5aeO+5/FmCiTkx/tGYbpE2emfcQ+oNdAKRhIEjIAfItrU4Bj2nQZdiiY0tFEfT
hn5HZ0X1i1yi63nxVGQH+oQQH9+ccPk87cIRLf3IK1B3M0J0j11XDhQdIXwAx9hV
52GXgkk0NX7EtT5Cq3x0Q513e70QA9ua1lt8yaCynkLrYKmMQQCKsLlJDSh+sUyu
ndiVl0g73cUPd962Tp/WCLOV4/DWShfZexfjoibjCkR81OVa9cguYITCXV3QGRCM
wo09DI/INOs1s6FS4ZKugpwgKEX6knh0Fo1i6DdVJQfeQvUo+MhbFjjK0SXT4QWc
4rlQv0Q1YoNn1EzFzsVwx7PhtU9wo4PU1978+582mrJBjteIN9a8z+7lZT1qKynD
BG3XUjnWAq4k5KUj5mEJkSSs2R2AIhHNiSmwmcuzHf67er1KrWvL+g8AXXJ8xLjh
P6ImJeMoEI7P2zb4FvSkQFF5SDjmaPNPpo6xe330EdSSWZTZtcgc9yH++I8ZX9Kb
0UnWic5HTZOx0VLqEqDw+iWufnUDMvq98tGD5c+BQqqofBZae5YNYfko1tCGoz/3
ZygMcOdRqRugur5SiCZnYCnIeQvVNi7nwfp2Bb3K0XMCr12IdeRDuoe45MzoG9zD
hLk0Y3VHS3eANvEsBMAwcyTBjgs8Q3bHdHwnPjVcAo3auOkyXUHZ7DEIxnmvVfaS
-----END RSA PRIVATE KEY-----

由于加密,无法再使用asn1parse.


PKCS#8是用于编码私钥的无关标准。它实际上是一个包装器。PKCS#8 对象是一个 ASN.1 结构,其中包括一些类型信息,以及作为子对象的私钥。类型信息将声明“这是一个 RSA 私钥”。由于 PKCS#8 是基于 ASN.1 的,它会生成不可打印的二进制文件,因此 OpenSSL 会很高兴地将其再次包装在 PEM 对象中。

因此,这里是与上面相同的 RSA 私钥,作为 PKCS#8 对象,它本身是 PEM 编码的:

-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANDfed0Ovk38AjzL
Y/OuoZtp0t5Ll9J/YaxUnHYHYrGFrMvbPk65+Kf7CMnWFbXb/NbJCz/0IVhAWuJ9
74+sxQ50uK2yaUPeXiUFDb35cqyY9pu0g08lDSfh3GAEZ4XFMJ3iUnCLKyZWxc+c
2uBBNzHaeKusiTULvBJb0CL1bd9lAgMBAAECgYEAqIpK+Rb2dFLPM2MjCfR1rsQb
RQhWP6JNnBLCFXMsLfahUfVdN4VUoacslkDLT+1s/ZtIGpjRdzamn2/jKFnOvr8F
Q9f43UU5bxDk5ccwPh9dKLGtfS9g5PyFC4J6FMBexK2ORmrMJ0KwpX1gh25MejKM
vaMbpAG8VAruQdphTBkCQQD6unZv54EMmuCWpw84TYeQKRBxF5JbQ4ihSB3a0iI8
UvwdI3Aq+8AAf5AEovqO95wuzAmd55yifxmOyCfgr8SnAkEA1UO6W1tir+HcXkVs
BDivpwxDZmaaEwV23ytG8B0TPXwK6hsa8BDLfWFT8idbSf5nSgcKwiDKjBJ4IbBE
swlhEwJAYoNdAb7+T4uS7t6Y9lBQEW5xDV5rnPw99NC3GkEyPm2ErZY8/kaIPCni
1k+LDx1u+lwk8ywLuTUjP5yZPokRRQJAHHNfniZv4PTpuC3cvidtz4RETZnsfhMh
i54zZX8LfQ1aS2b4TgR/kSd10n1LoXBuCSMtXT6QpuUj36KrV5MtvwJBAL5AMXtj
WjgikMZy77daFL+kj8KRcPakMwkzqsYGAbqD0PVVM8LHQtkBYrgZ2YQuE8z7R48f
g/bj9Wwljia+61A=
-----END PRIVATE KEY-----

如您所见,PEM 标头中指示的类型不再是“RSA PRIVATE KEY”,而只是“PRIVATE KEY”。如果我们申请asn1parse它,我们会得到:

    0:d=0  hl=4 l= 631 cons: SEQUENCE       
    4:d=1  hl=2 l=   1 prim:  INTEGER           :00
    7:d=1  hl=2 l=  13 cons:  SEQUENCE       
    9:d=2  hl=2 l=   9 prim:   OBJECT            :rsaEncryption
   20:d=2  hl=2 l=   0 prim:   NULL           
   22:d=1  hl=4 l= 609 prim:  OCTET STRING      [HEX DUMP]:30820<skip...>

(我在最后一行删掉了很多字节)。我们看到该结构以一个标识符开头,该标识符表示“这是一个 RSA 私钥”,并且私钥本身作为一个包含在内OCTET STRING(并且该字符串的内容正是上述基于 ASN.1 的结构)。

PKCS#8 可选地支持基于密码的加密这是一种非常开放的格式,因此它可能与世界上所有基于密码的加密系统兼容,但软件必须支持它。OpenSSL 支持旧的 DES+MD5 加密,或更新的PBKDF2和可配置的算法。DES(不是 3DES)是一个小问题:DES 相对较弱,因为它的小密钥大小(56 位)使得突破穷举搜索在技术上是可行的(已经完成);但是,对于业余爱好者来说,这将是相当昂贵的。不过,最好使用 PBKDF2 和更好的加密算法。

给定如上所示的原始私钥,这是一个 OpenSSL 命令行,它将其转换为 PKCS#8 对象,具有 3DES 加密和 PBKDF2 用于基于密码的密钥派生:

openssl pkcs8 -topk8 -in keyraw.pem -out keypk8.pem -v2 des3

产生:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIZT3rvVU85p0CAggA
MBQGCCqGSIb3DQMHBAgtYXWrNG+OYgSCAoCewt8WkgCDaBCSOoe88WTpV2haxUFW
iWkdJQtEkzkpYnwA0E0Bj5CBnSd3EdSRmup0rP9WxzdMe+qx2N+GGLTcmA7pMyBV
XK9OTdiixMWvlG64lrLFtQxoKaxo48zUVobLuRrtaVLvwZ7OpO4hA2zsl6qaWaV7
8GEiAWz28K3DIBDVr1CKpEdFf7epkC7e1/ojJDNAwPiE9rxkaqGHpogqJQKb5s8X
ZyGhVG3rPuwgOxhU5d1G7K6+N9wKYkZXiCmsoqZxD94M3QH8sM8YF41rxBsbPSJ/
7JgGQMOJQxxrdeHSAt5P1iasI7lNXa7HacTZl1nPDXpnpjKA5E/jNMf1EgV+sN3f
pL4GoFvw8zImOF4OHdo9KBz61oKFylQrGQM6WhCsTqsSVZxR0tH8ERSOhhWn2wmy
NgiagfVT4nED9XFInEwTKoXKUjTSOHUmbTl/HF637NrYjSBLgT/e+XBQBmFMSaNc
+KLlJRHpjB8QZ8cIdDFwVIYkmm4Po7h1uYob1d2/4saxjHrtZ8f7GqmT/SGXMpj5
eL0bXDXdjcapDkLx5X0/BYI3AYTlFXEZU0UJT8aad0Fiygw1bLVDR8yDl63Bthlb
gS15LhjqGYGhgX3tARS94HtBvlSAtgV6AB5QjEJfU7jgyu0lFn1hTULmwFJVkjj6
Oy2WeuHseOZ1X45V7DvNcS1iT7fttwQZoSvdks8WulsodpOr7sbtaJbsUUToTxIN
GtNQo9Ce/QAeONmSf8G9jbBURBmLH+kzzzptYcCsVaaUnWPpgebH/WJRa83quPw6
fwy3xZgg9pPHFBiFAG2c3Uuelat/eXhXdW74XlDgOIpmbMfsDxaVOiuM
-----END ENCRYPTED PRIVATE KEY-----

所以现在这是一个“加密的私钥”。让我们看看asn1parse能说些什么:

    0:d=0  hl=4 l= 710 cons: SEQUENCE      
    4:d=1  hl=2 l=  64 cons:  SEQUENCE       
    6:d=2  hl=2 l=   9 prim:   OBJECT            :PBES2
   17:d=2  hl=2 l=  51 cons:   SEQUENCE       
   19:d=3  hl=2 l=  27 cons:    SEQUENCE       
   21:d=4  hl=2 l=   9 prim:     OBJECT            :PBKDF2
   32:d=4  hl=2 l=  14 cons:     SEQUENCE       
   34:d=5  hl=2 l=   8 prim:      OCTET STRING      [HEX DUMP]:653DEBBD553CE69D
   44:d=5  hl=2 l=   2 prim:      INTEGER           :0800
   48:d=3  hl=2 l=  20 cons:    SEQUENCE          
   50:d=4  hl=2 l=   8 prim:     OBJECT            :des-ede3-cbc
   60:d=4  hl=2 l=   8 prim:     OCTET STRING      [HEX DUMP]:2D6175AB346F8E62
   70:d=1  hl=4 l= 640 prim:  OCTET STRING      [HEX DUMP]:9EC2DF16920<skip...>

我们在那里看到使用了 PBKDF2。其中OCTET STRING的内容653DEBBD553CE69D是 PBKDF2 的盐。INTEGER0800(即 2048 的十六进制)是迭代计数。加密本身在 CBC 模式下使用 3DES,具有自己随机生成的 IV ( 2D6175AB346F8E62)。没关系。PBKDF2 默认使用 SHA-1,这不是问题。

碰巧的是,虽然 OpenSSL 支持某种任意的迭代计数(好吧,将其保持在 20 亿以下以避免 32 位有符号整数的问题),openssl pkcs8命令行工具不允许您从默认的 2048 更改迭代计数,除了将其设置为 1(使用-noiter选项)。所以这是 2048 或 1,仅此而已。2048 比 1 好得多(比方说,它好 2048 倍),但按照今天的标准,它仍然很低。


总结: OpenSSH 可以接受原始 RSA/PEM 格式的私钥、带加密的 RSA/PEM、不带加密的 PKCS#8 或带加密的 PKCS#8(可以是“旧式”或 PBKDF2)。对于私钥的密码保护,防止可能窃取您的私钥文件副本的攻击者,您真的想使用最后一个选项:PKCS#8 与 PBKDF2 加密。不幸的是,使用openssl命令行工具,您无法对 PBKDF2 进行太多配置。您不能选择散列函数(即 SHA-1,仅此而已 - 这不是一个真正的问题),更重要的是,您不能选择迭代次数,默认值为 2048,这对于舒适性来说有点低。

您可以使用其他一些具有更高 PBKDF2 迭代次数的工具来加密您的密钥,但我不知道有任何现成的工具可以用于此目的。这将是使用加密库进行一些编程的问题。

无论如何,你最好有一个强密码。15 个随机小写字母(易于输入,不难记住)将提供 70 位的熵,这足以阻止攻击者,即使使用了错误的密码推导(迭代次数为 1)。

较新的 OpenSSH客户端版本 (>= 6.5) 支持较新的 OpenSSH 专用私钥格式,该格式使用正确的密钥派生函数。目前仅支持 bcrypt kdfname(请参阅本规范文档)。OpenSSH 7.8 开始默认使用这种格式

对于旧版本(< 7.8),将-o选项传递ssh-keygen,这将强制使用新的 OpenSSH 格式。生成的私钥如下所示:

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABCm1gDucj
atDrjXom9j2Hb8AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCquRcOc4bw
...
59VrTpkgqVZYc88GJwFFU+8awxP0qm+ZBHdR/ZlcCHdYt4XmIbfIR8GPtNQQ84Ft7YbMTF
mqCJOcWg==                                                                 
-----END OPENSSH PRIVATE KEY-----

这篇文章提到了其他注意事项,例如使用带有-a选项的自定义轮数(另请参见 的手册页ssh-keygen)。

如果您有一个具有更高迭代次数的旧 PKCS#8 密钥,请先对其进行解密,然后通过设置密码将其转换为新格式:

# WARNING: /tmp/mykey will contain the unencrypted key.
# If /tmp is a tmpfs, this is ok.
openssl pkcs8 -in ~/.ssh/id_rsa -out /tmp/mykey
ssh-keygen -p -f /tmp/mykey
# validate the key and then move back:
mv /tmp/mykey ~/.ssh/id_rsa