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

2021-12-26 00:00:00 postgresql security php pdo pgp

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

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

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

我搜索了最佳实践,但找不到太多.

class DB {受保护的 $_config;受保护的 $_iUserId;受保护的 $_iServerId;受保护的 $_dbConn;受保护的 $_sPubKey;受保护的 $_sPrivKey;公共函数 __construct($iUserId, $iServerId) {//将全局配置数组引入本地范围全局 $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"));}//连接数据库公共函数连接(){尝试 {$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 连接对象已创建";} catch(PDOException $e) {echo $e->getMessage();}}公共函数 insertServerDef($sHost, $iPort, $sUser, $sPass) {//测试$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_earencrypt(:pass,d(: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();}公共函数 getServerDef($iServerId) {$oStmt = $this->_dbConn->prepare(" SELECT server_id, pgp_pub_decrypt(pass,dearmor(:priv_key),:priv_key_pass) 作为decryptpass从 upze_server_def 美元哪里 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>";}}//关闭任何现有的数据库连接公共函数关闭(){$this->_dbConn = null;}//在卸载时关闭任何现有的数据库连接公共函数 __destruct() {$this->_dbConn = null;}}

解决方案

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

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

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

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

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

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

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

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

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

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

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

I have a web application that needs to store server login information. I'm using a 2048bit PGP public key to encrypt inserted passwords (see the insertServerDef) and a private key with a passphrase to decrypt the passwords (see getServerDef).

As I understand things, the weakest link in this chain is the handling of the private key and passphrase. As you can see from my code below, I'm just using file_get_contents to retrieve the key and passphrase from files located in the current web directory--not good.

My question is: what is a good method for securely retrieving the private key and passphrase for use in decrypting login info? Maybe I should store/retrieve the private key via an authenticated remote file server?

I've searched for best practices, but haven't been able to find much.

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

}

解决方案

(Note: I'm no security expert. I have an interest in the area, but that's it. Keep that in mind.)

If possible, don't store passwords at all

It depends a lot on what your needs are. The best option of all is not to use two-way encryption at all; if you can store only salted and one-way-hashed password digests that's ideal. You can still test them to see if they match a supplied password from the user, but you never store it.

Better still, if your clients use some sane protocol (ie: not HTTP as commonly implemented) you can use a challenge-response authentication mechanism that means your app never ever needs to see the user's password, not even when authenticating them. Sadly this is rarely possible on the public web, which has security that'd put 80's programmers to shame.

If you must store the password, isolate the keys from the app

If you must be able to decrypt the passwords, ideally you shouldn't have all the details to do so in one place, and certainly not one copyable, easily accessible place.

For that reason I'd personally prefer not to use PgCrypto (as you're doing) for this purpose because it forces you to reveal the private key and (if it has one) passphrase to the server, where it could be exposed in PostgreSQL's log files or otherwise potentially sniffed. I'd want to do my crypto client-side, where I could use PKCS#11, a key agent, or other tools that let me decrypt the data without ever having my code able to access the key.

The problem of secure key storage is part of what PKCS#11 was invented for. It provides a generic interface for applications and crypto providers to talk to anything that can provide certain signing and decryption services without ever revealing its key. The usual, but not only, use is with hardware based crypto like smart cards and hardware crypto modules. Such devices can be told to sign or decrypt data passed to them, and can do so without ever revealing the key. If possible, consider using a smartcard or HSM. As far as I know PgCrypto cannot use PKCS#11 or other HSMs/smartcards.

If you can't do that, you can still probably use a key management agent, where you load your key into a key management program manually when the server boots, and the key management program provides a PKCS#11 (or some other) interface for signing and decryption via a socket. That way your web app never needs to know the key at all. gpg-agent may qualify for this purpose. Again, as far as I know PgCrypto cannot use a key management agent, though it'd be a great feature to add.

Even a small improvement can help. It's best if the passphrase for your key isn't stored on disk, so you might require it to be entered when the app is started up so the key can be decrypted. You're still storing the decrypted key in memory, but all the details to decrypt it are no longer on disk and easy to get at. It's much harder for an attacker to steal the decrypted key from memory than to grab a "password.txt" from disk.

What you choose to do depends a lot on the details of your security needs and the data you're working with. In your position I'd just not store the passwords if at all possible, and if I had to I'd want to use a PKCS#11-compatible hardware device.

相关文章