存储/检索 PGP 私钥和密码的安全方法?

Posted

技术标签:

【中文标题】存储/检索 PGP 私钥和密码的安全方法?【英文标题】:Secure method for storing/retrieving a PGP private key and passphrase? 【发布时间】:2012-09-02 00:27:13 【问题描述】:

我有一个需要存储服务器登录信息的 Web 应用程序。我正在使用 2048 位 PGP 公钥来加密插入的密码(参见 insertServerDef)和带有密码的私钥来解密密码(参见 getServerDef)。

据我了解,这条链中最薄弱的环节是私钥和密码的处理。正如您从下面的代码中看到的那样,我只是使用file_get_contents 从位于当前 Web 目录中的文件中检索密钥和密码——不好。

我的问题是:安全检索用于解密登录信息的私钥和密码的好方法是什么?也许我应该通过经过身份验证的远程文件服务器存储/检索私钥?

我搜索了最佳做法,但找不到太多。

class DB 

    protected $_config;
    protected $_iUserId;
    protected $_iServerId;
    protected $_dbConn;
    protected $_sPubKey;
    protected $_sPrivKey;


    public function __construct($iUserId, $iServerId) 

        //bring the global config array into local scope
        global $config;
        $this->_config = $config;

        $this->_iUserId = $iUserId;
        $this->_iServerId = $iServerId;

        $this->_sPubKey = file_get_contents("public_key");
        $this->_sPrivKey = file_get_contents("private_key");
        $this->_sPrivKeyPass = trim(file_get_contents("private_key_pass"));

    

    //connect to the database
    public function connect() 
        try 


            $this->_dbConn = new PDO("pgsql:host=".$this->_config['db_host']." dbname=".$this->_config['db_name'],$this->_config['db_username'],$this->_config['db_password']);

            echo "PDO connection object created";
         catch(PDOException $e) 

            echo $e->getMessage();

        

    

    public function insertServerDef($sHost, $iPort, $sUser, $sPass) 

        //testing
        $iUserId = 1;

        $oStmt = $this->_dbConn->prepare("INSERT INTO upze_server_def (server_id, host_address, ssh_port, username, pass, user_id) VALUES (DEFAULT, :host_address, :ssh_port, :username, pgp_pub_encrypt(:pass,dearmor(:pub_key)), :user_id)");
        $oStmt->bindParam(':host_address',$sHost);
        $oStmt->bindParam(':ssh_port',$iPort);
        $oStmt->bindParam(':username',$sUser);
        $oStmt->bindParam(':pass',$sPass);
        $oStmt->bindParam(':pub_key',$this->_sPubKey);

        $oStmt->bindParam(':user_id',$iUserId);
        $oStmt->execute();

    

    public function getServerDef($iServerId) 

        $oStmt = $this->_dbConn->prepare("  SELECT server_id, pgp_pub_decrypt(pass,dearmor(:priv_key),:priv_key_pass) As decryptpass 
                                            FROM upze_server_def usd 
                                            WHERE usd.server_id = :server_id
                                        ");

        $oStmt->bindParam(':server_id', $iServerId);
        $oStmt->bindParam(':priv_key', $this->_sPrivKey);
        $oStmt->bindParam(':priv_key_pass', $this->_sPrivKeyPass);
        $oStmt->execute();

        while($row = $oStmt->fetch()) 
            echo "<pre>".print_r($row)."</pre>";
        

    

    //close any existing db connection
    public function close() 
        $this->_dbConn = null;
    


    //close any existing db connections on unload
    public function __destruct() 
        $this->_dbConn = null;
    


【问题讨论】:

+1 用于思考问题,希望我能 +more。为什么需要存储密码明文,而不是加盐和散列的密码摘要? 我确实需要双向加密,因为我的应用程序使用服务器登录凭据通过 SSH 连接。对于用户帐户(即应用程序的注册用户),我只需要单向加密,我将存储加密和散列的密码。 【参考方案1】:

我可能正在做与你类似的事情。我目前希望保护本地 Web 服务器数据库上的个人信息,因此我使用公钥(存储在 Web 服务器本身上)对其进行加密,并使用存储在 cookie 中的私钥对其进行解密,其生命周期很短(30 分钟)我)。

通过 SSL 连接,这将防止密钥落入坏人之手,并且不会将其存储在服务器上。理想情况下,我应该仔细检查 php 是否不会在服务器上缓存 cookie 值,但即使这样做,与简单地窃取明文数据库相比,这个安全层对于攻击者来说仍然是一个更大的障碍。

这对您来说是否是一个好方法取决于您的应用程序是否需要访问服务器凭据,即使用户未通过 Web 登录也是如此。就我而言,只需要通过网络应用程序进行解密,因此 cookie 就足够了。但是,如果您需要无人值守使用,则需要将私钥存储在服务器上。

【讨论】:

赞成--这个策略对我不起作用,因为我需要无限期地存储登录凭据,但这听起来像是临时会话的好策略。【参考方案2】:

我相信除非你描述你想要避免的威胁场景,否则没有答案。

让我重新表述一下您的情况:您需要一个纯文本密码才能通过 SSH 访问远程系统。目标是保护此密码,但在需要时提供。

确实没有办法既保护密码又能以纯文本形式使用。总是有办法解密它,这需要机制和密钥。

您可以尝试对此进行一些迭代,例如再次保护此机密,但最后您需要将最终的纯文本密码存储到变量中并将其传递给身份验证方案。

您想要避免的威胁场景是什么?

有人窃取了您的数据库转储,但不应该知道存储的密码 有人闯入您的服务器并使用秘密密码读取数据库和文件 有人闯入您的服务器,更改您的脚本并在使用解密的纯文本密码的地方进行拦截

你看,总有办法造成伤害。如果您不定期检查系统上的不规则活动,则可能造成大多数伤害。就像监视所有日志文件的攻击一样。可能会将日志发送到另一个系统日志服务器,这样如果它们在同一台服务器上,攻击者就无法更改它们。如果您尽一切可能阻止攻击者进入您的系统,那么安全存储密码短语的需求就会减少。

将密码短语存储到 RAM 中可能是一个不错的主意,例如存储在 RAM 磁盘或专用内存中。这样,如果服务器被盗,它很可能会断电并忘记密码。但是您必须有办法从远程恢复密码以在重新启动后继续操作。但同样:如果您无法检测到攻击者在您的系统上,那么无论密码是在 RAM 中还是在磁盘上都没有意义 - 可以读取。

我想知道您为什么首先要处理密码。如果我使用 SSH,我总是尝试使用 SSH 密钥进行加密身份验证。这在目标服务器上更安全,因为一个帐户(如 root)可以拥有多个 SSH 密钥,如果存储在您服务器上的一个被泄露,可以将其删除而不会干扰其他密钥。是的,这些 SSH 密钥也可以使用密码保护。

【讨论】:

【参考方案3】:

(注意:我不是安全专家。我对该领域感兴趣,但仅此而已。请记住这一点。)

如果可能,不要存储密码

这在很大程度上取决于您的需求。最好的选择是根本不使用双向加密;如果您只能存储 salted 和 one-way-hashed 密码摘要,那就太理想了。您仍然可以测试它们以查看它们是否与用户提供的密码匹配,但您永远不会存储它。

更好的是,如果您的客户端使用一些健全的协议(即:不是通常实现的 HTTP),您可以使用challenge-response authentication mechanism,这意味着您的应用永远永远需要查看用户的密码,而不是即使在对它们进行身份验证时。遗憾的是,这在公共网络上几乎不可能实现,其安全性让 80 年代的程序员感到羞耻。

如果您必须存储密码,请将密钥与应用程序隔离

如果您必须能够解密密码,理想情况下,您不应该将所有详细信息放在一个地方,当然也不能放在一个可复制、易于访问的地方。

出于这个原因,我个人不希望为此目的使用 PgCrypto(就像你正在做的那样),因为它会迫使你向服务器透露私钥和(如果有的话)密码,它可能在哪里暴露在 PostgreSQL 的日志文件中或以其他方式可能被嗅探。我想做我的加密客户端,在那里我可以使用 PKCS#11、密钥代理或其他工具让我解密数据而无需我的代码能够访问密钥。

安全密钥存储问题是 PKCS#11 发明的一部分。它为应用程序和加密提供者提供了一个通用接口,可以与任何可以提供特定签名和解密服务的对象进行通信而无需透露其密钥。通常但不仅限于使用基于硬件的加密,如智能卡和硬件加密模块。可以告诉这些设备对传递给它们的数据进行签名或解密,并且可以在不泄露密钥的情况下这样做。如果可能,请考虑使用智能卡或 HSM。据我所知,PgCrypto 不能使用 PKCS#11 或其他 HSM/智能卡。

如果您不能这样做,您仍然可以使用密钥管理代理,在服务器启动时手动将密钥加载到密钥管理程序中,并且密钥管理程序提供 PKCS#11(或一些其他)通过套接字进行签名和解密的接口。这样,您的 Web 应用程序就根本不需要知道密钥。 gpg-agent 可能符合此目的。同样,据我所知,PgCrypto 不能使用密钥管理代理,尽管添加它会是一个很棒的功能。

即使是很小的改进也会有所帮助。最好不要将密钥的密码存储在磁盘上,因此您可能需要在应用程序启动时输入它,以便可以解密密钥。您仍然将解密的密钥存储在内存中,但解密它的所有详细信息不再在磁盘上并且很容易获得。攻击者从内存中窃取解密的密钥比从磁盘中获取“password.txt”要困难得多。

您选择做什么在很大程度上取决于您的安全需求的详细信息以及您正在使用的数据。在您的位置上,如果可能的话,我只是不存储密码,如果必须,我想使用与 PKCS#11 兼容的硬件设备。

【讨论】:

这篇文章很长,所以我认为值得强调的是,唯一真正好的解决方案是根本不存储密码。否则,您只是将您的信任巩固到一个密钥中,问题就变成了“我将那个密钥放在哪里?”,所有简单、易于实施的解决方案(PKCS#11 都不是这些)相当于将密钥放在门垫。 @PhilFrost 是的。使用智能卡或 USB 加密引擎的 PKCS#11 实际上在使用 Java 时非常容易实现,如果使用 GnuPG 则相当容易(对于 GnuPG)。否则就太可怕了。

以上是关于存储/检索 PGP 私钥和密码的安全方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何存储/检索 RSA 公钥/私钥

双向加密:我需要存储可以检索的密码

双向加密:我需要存储可以检索的密码

当您尝试使用 PIV 卡的私钥和全局密码进行签名时,RSACryptoServiceProvider 是不是有效?

将私钥存储在数据库中

hbuilder打包ios需要的私钥和profile文件和私钥证书怎么获得