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;
    }

}
4

3 回答 3

8

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

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

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

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

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

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

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

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

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

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

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

于 2012-09-08T10:03:29.940 回答
3

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

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

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

于 2013-01-10T22:38:29.600 回答
2

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

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

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

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

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

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

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

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

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

于 2013-01-09T21:58:35.130 回答