Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
371 views
in Technique[技术] by (71.8m points)

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

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

}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

(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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...