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

时间:2012-09-08 09:40:05

标签: php security postgresql pdo pgp

我有一个需要存储服务器登录信息的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;
    }

}

3 个答案:

答案 0 :(得分:8)

(注意:我不是安全专家。我对该领域很感兴趣,但就是这样。记住这一点。)

如果可能,请不要存储密码

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

更好的是,如果您的客户使用一些理智的协议(即:不是通常实施的HTTP),您可以使用challenge-response authentication mechanism,这意味着您的应用永远不会需要查看用户的密码,即使在验证它们时也是如此。遗憾的是,在公共网站上很少有这种情况,因为它的安全性让80位程序员感到羞耻。

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

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

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

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

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

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

您选择做的事情很大程度上取决于您的安全需求和您正在使用的数据的详细信息。在你的位置,如果可能的话,我不会存储密码,如果我不得不想要使用与PKCS#11兼容的硬件设备。

答案 1 :(得分:3)

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

通过SSL连接,这将使密钥落入坏人之手,并且不会将密钥存储在服务器上。理想情况下,我应该仔细检查PHP不会在服务器上缓存cookie值,但即使这样,这个安全层仍然是攻击者的一个更大的障碍,而不仅仅是窃取明文数据库。

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

答案 2 :(得分:2)

我认为除非您描述想要避免的威胁情景,否则无法找到答案。

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

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

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

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

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

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

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

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