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;
}
}
What is the most secure way to store my private PGP keys? The safest place to store a PGP key is on your computer. If you put it on a USB device, you might lose it. If you use a password manager, you might forget the password for your password manager or your computer might break then you lose all your passwords.
A PGP passphrase is used to encrypt your private key, which in turn is used to encrypt your emails. The password is used to log in to your StartMail account. When you create a StartMail account, a PGP passphrase will automatically be configured upon account creation.
Obtain your Private KeyRight-click on your key and select Export Secret Keys. An Export Secret Certificate dialog box displays. Click the to select where you want to save the key and then click the ASCII armor check box.
(Note: I'm no security expert. I have an interest in the area, but that's it. Keep that in mind.)
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 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With