用于存储/检索PGP私钥和密码短语的安全方法


Secure method for storing/retrieving a PGP private key and passphrase?

我有一个web应用程序,需要存储服务器登录信息。我使用2048bit 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;
    }
}

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

如果可能的话,根本不要存储密码

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

更好的是,如果你的客户端使用一些合理的协议(即:不是通常实现的HTTP),你可以使用质询-响应身份验证机制,这意味着你的应用程序永远不需要查看用户的密码,甚至在对他们进行身份验证时也不需要。遗憾的是,这在公共网络上几乎不可能实现,因为公共网络的安全性让80年代的程序员感到羞愧。

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

如果你必须能够解密密码,那么理想情况下,你不应该把所有的细节都放在一个地方,当然也不是一个可复制、易于访问的地方。

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

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

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

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

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

我可能正在做与您类似的事情。我目前希望保护本地网络服务器数据库中的个人信息,因此我使用公钥(存储在网络服务器上)对其进行加密,并使用存储在cookie中的私钥对其进行解密,使用寿命较短(对我来说为30分钟)。

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

这是否适合您,取决于您的应用程序是否需要访问服务器凭据,即使用户没有通过web登录。在我的情况下,解密只需要通过网络应用程序,所以cookie就足够了。但是,如果您需要无人参与使用,则需要将私钥存储在服务器上。

我相信,除非你描述了你想要避免的威胁场景,否则无法找到答案。

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

真的没有办法既保护密码又能以明文形式使用。总有一种方法可以解密它,这需要机制和密钥。

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

您希望避免的威胁场景是什么?

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

你看,总是有办法造成伤害的。如果你不定期检查系统中的不正常活动,可能会造成最大的伤害。比如监视所有日志文件以防攻击。可能会将日志发送到另一个系统日志服务器,这样攻击者就无法在同一服务器上更改日志。如果你尽一切努力防止攻击者进入你的系统,那么安全存储密钥短语的必要性就会减少。

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

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