我必须将私钥存储在文件中吗?

我必须将私钥存储在文件中吗?

我有一个数据库驱动的应用程序,需要通过 ssh 与另一台服务器进行通信。 Web 应用程序可以生成密钥对并向用户提供公钥,也可以接受 Web 表单字段中未加密的私钥。然后它使用可逆加密来加密密钥并将其存储在数据库中。

当 Web 应用程序与 ssh 服务器通信时,我需要解密私钥并以某种方式将其提供给 ssh。我可以使用 创建一个安全的临时文件mktemp,将 ssh 指向它,然后销毁该文件,但这是额外的工作,以一种新的方式公开密钥,似乎没有必要。我已经查看了man sshman ssh-agentman ssh-add,但似乎没有任何方法可以在不先将密钥添加到文件中的情况下使用该密钥。我缺少什么?

答案1

ssh如果您想使用/等现有工具,ssh-agent您必须以文件形式提供密钥。

另一种可能更可行的解决方案是直接ssh在应用程序中实现客户端并依赖第三方库,例如杰士或扩展ssh/ssh-agent以直接从数据库接收和解密密钥。

答案2

文件是应用程序之间交换数据的主要方式。它不一定是磁盘文件。它可以是临时存储上的文件或管道。ssh-add只要密钥文件不受密码保护,您就可以将密钥文件输入其标准输入(并且由于密钥文件是加密存储的,因此没有理由对其进行密码保护)。

或者,不要做任何花哨的事情,并将文件写入内存支持的文件系统,例如/run(或/dev/shm/tmp或您的发行版提供的任何内容)。

或者,保留私钥文件,不进行外部加密,但在其上设置密码。使用随机生成的长密码,并将该密码存储在数据库中。

1实验上,ssh-add受密码保护且不可查找的密钥文件(例如命名管道)会被阻塞。

答案3

抱歉,不是答案,但评论太长,我认为评论已经到位。

重要的是您试图通过将密钥加密存储在数据库中来实现什么目的。

能够访问临时私钥文件的攻击者也将能够读取 ssh 客户端进程的内存(因此无论如何都能获取密钥数据),因为这两个数据必须具有基本相同的访问权限 - 尝试隐藏文件似乎并没有使其变得更安全。

如果您使用不同的用户进行数据库、Web 应用程序和 ssh 连接,则通过将密钥存储在数据库中、在 Web 应用程序中解密并将其提供给 ssh,您只是通过在整个过程中传播密钥来打开潜在的攻击向量。如果您只使用一个用户来执行所有这些操作(db、app、ssh),那么除了代码复杂性之外,您什么也得不到。

唯一的优点似乎是可以更轻松地将系统转移到不同的主机,并在数据库数据被盗但 Web 应用程序(AFAIU 包含解密算法和密码)被盗的情况下获得潜在收益。但这有可能吗?

也就是说,如果您想保护密钥,您还可以使用 ssh 的内部加密密钥,并在启动服务时将它们加载到 ssh 代理中(记住在服务停止时将其删除!)。但请再次记住,ssh-agent 将密钥保存在内存中未加密,因此可能会被读取。再说一遍:是您想解决的问题是什么?

如果您只需要保护两台机器之间的通信,stunnel可能比 ssh 提供更好的服务。

最后的问题是:就开放SSH便携的) 而言,编写一个简单的补丁,允许像使用该选项ssh一样从另一个进程读取密钥应该相当简单。sshdAuthorizedKeysCommand

答案4

当我使用 Python 生成密钥对并将其存储在数据库中时,我试图解决这个问题。以下是我最终使用的一些步骤,即启动ssh-agent并与之交互。它是用 Python 编写的,但想必你可以将其翻译成其他任何语言。另一个特点是这些都不使用shell=True,因此它们更安全,免受注入攻击。

import subprocess
import os
sockfile = '/some/place.sock'

agent = subprocess.Popen(['ssh-agent',
          '-D', # foreground mode
          '-a', sockfile, # bind address (socket file)
     ])
subprocess.check_call(['ssh-add', 
           '-D', # delete all identities from the agent
      ], 
      env={'SSH_AUTH_SOCK': sockfile},
)

这会启动一个ssh-agent仅适用于我们的程序并将其清除的程序(我相信它正在将我的用户密钥加载到其中,但不确定。)SSH_AUTH_SOCK通常设置的环境变量ssh-agent(但我们手动设置),我们将其提供给其他程式。

接下来,我们ssh-add向它提供私钥(这里它存储dr.private_key为字符串,但我需要对其进行序列化,因此我将其编码为 ASCII)。

adder = subprocess.Popen(['ssh-add', 
        '-', # read key from stdin
     ], 
     env={'SSH_AUTH_SOCK': sockfile}, 
     stdin=subprocess.PIPE,
)
adder.communicate(dr.private_key.encode('ascii'))
retval = adder.wait()
assert retval == 0 # added successfully

现在,每当我使用一些使用 SSH 的应用程序(例如 Git)时,我都会引用它使用相同的 SSH 代理套接字:

gitp = subprocess.Popen(
        ['git', '-C', os.path.abspath('../some-repo') ,'fetch'], 
        env={'SSH_AUTH_SOCK': sockfile}, 
        stdout=subprocess.PIPE, 
        stderr=subprocess.STDOUT,
    )
print(gitp.communicate()[0].decode('ascii'))

而且效果很好。唯一的“临时文件”是 所使用的套接字ssh-agent,但它的创建是为了只有所有者才有权访问它,并且当您终止代理时它会被删除。

相关内容