I am creating an application that will store passwords, which the user can retrieve and see. The passwords are for a hardware device, so checking against hashes are out of the question.

What I need to know is:

  1. How do I encrypt and decrypt a password in PHP?

  2. What is the safest algorithm to encrypt the passwords with?

  3. Where do I store the private key?

  4. Instead of storing the private key, is it a good idea to require users to enter the private key any time they need a password decrypted? (Users of this application can be trusted)

  5. In what ways can the password be stolen and decrypted? What do I need to be aware of?

Comments

Ugh. For one moment I thought this question was about PHP 2 ;)

Written by Franz

I'm impressed! A question about encrypting passwords where the person asking the question actually knows the definition of "encrypt" (people often mean "hash") and they understand that there are implications to storing the secret key. I salute you!

Written by Adam Paynter

Accepted Answer

Personally, I would use mcrypt like others posted. But there is much more to note...

  1. How do I encrypt and decrypt a password in PHP?

    See below for a strong class that takes care of everything for you:

  2. What is the safest algorithm to encrypt the passwords with?

    safest? any of them. The safest method if you're going to encrypt is to protect against information disclosure vulnerabilities (XSS, remote inclusion, etc). If it gets out, the attacker can eventually crack the encryption (no encryption is 100% un-reversible without the key - As @NullUserException points out this is not entirely true. There are some encryption schemes that are impossible to crack such as OneTimePad).

  3. Where do I store the private key?

    What I would do is use 3 keys. One is user supplied, one is application specific and the other is user specific (like a salt). The application specific key can be stored anywhere (in a config file outside of the web-root, in an environmental variable, etc). The user specific one would be stored in a column in the db next to the encrypted password. The user supplied one would not be stored. Then, you'd do something like this:

    $key = $userKey . $serverKey . $userSuppliedKey;
    

    The benefit there, is that any 2 of the keys can be compromised without the data being compromised. If there's a SQL Injection attack, they can get the $userKey, but not the other 2. If there's a local server exploit, they can get $userKey and $serverKey, but not the third $userSuppliedKey. If they go beat the user with a wrench, they can get the $userSuppliedKey, but not the other 2 (but then again, if the user is beaten with a wrench, you're too late anyway).

  4. Instead of storing the private key, is it a good idea to require users to enter the private key any time they need a password decrypted? (Users of this application can be trusted)

    Absolutely. In fact, that's the only way I would do it. Otherwise you'd need to store an unencrypted version in a durable storage format (shared memory such as APC or memcached, or in a session file). That's exposing yourself to additional compromises. Never store the unencrypted version of the password in anything except a local variable.

  5. In what ways can the password be stolen and decrypted? What do I need to be aware of?

    Any form of compromise of your systems will let them view encrypted data. If they can inject code or get to your filesystem, they can view decrypted data (since they can edit the files that decrypt the data). Any form of Replay or MITM attack will also give them full access to the keys involved. Sniffing the raw HTTP traffic will also give them the keys.

    Use SSL for all traffic. And make sure nothing on the server has any kind of vulnerabilities (CSRF, XSS, SQL Injection, Privilege Escalation, Remote Code Execution, etc).

Edit: Here's a PHP class implementation of a strong encryption method:

/**
 * A class to handle secure encryption and decryption of arbitrary data
 *
 * Note that this is not just straight encryption.  It also has a few other
 *  features in it to make the encrypted data far more secure.  Note that any
 *  other implementations used to decrypt data will have to do the same exact
 *  operations.  
 *
 * Security Benefits:
 *
 * - Uses Key stretching
 * - Hides the Initialization Vector
 * - Does HMAC verification of source data
 *
 */
class Encryption {

    /**
     * @var string $cipher The mcrypt cipher to use for this instance
     */
    protected $cipher = '';

    /**
     * @var int $mode The mcrypt cipher mode to use
     */
    protected $mode = '';

    /**
     * Constructor!
     *
     * @param string $cipher The MCRYPT_* cypher to use for this instance
     * @param int    $mode   The MCRYPT_MODE_* mode to use for this instance
     */
    public function __construct($cipher, $mode) {
        $this->cipher = $cipher;
        $this->mode = $mode;
    }

    /**
     * Decrypt the data with the provided key
     *
     * @param string $data The encrypted datat to decrypt
     * @param string $key  The key to use for decryption
     * 
     * @returns string|false The returned string if decryption is successful
     *                           false if it is not
     */
    public function decrypt($data, $key) {
        $key = $this->stretch($key);
        $iv = $this->getIv($data, $key);
        if ($iv === false) {
            return false; //Invalid IV, so we can't continue
        }
        $de = mcrypt_decrypt($this->cipher, $key, $data, $this->mode, $iv);
        if (!$de || strpos($de, ':') === false) return false;

        list ($hmac, $data) = explode(':', $de, 2);
        $data = rtrim($data, "\0");

        if ($hmac != hash_hmac('sha1', $data, $key)) {
            return false;
        }
        return $data;
    }

    /**
     * Encrypt the supplied data using the supplied key
     * 
     * @param string $data The data to encrypt
     * @param string $key  The key to encrypt with
     *
     * @returns string The encrypted data
     */
    public function encrypt($data, $key) {
        $key = $this->stretch($key);
        $data = hash_hmac('sha1', $data, $key) . ':' . $data;

        $iv = $this->generateIv();
        $enc = mcrypt_encrypt($this->cipher, $key, $data, $this->mode, $iv);

        return $this->storeIv($enc, $iv, $key);
    }

    /**
     * Generate an Initialization Vector based upon the class's cypher and mode
     *
     * @returns string The initialization vector
     */
    protected function generateIv() {
        $size = mcrypt_get_iv_size($this->cipher, $this->mode);
        return mcrypt_create_iv($size, MCRYPT_RAND);
    }

    /**
     * Extract a stored initialization vector from an encrypted string
     *
     * This will shorten the $data pramater by the removed vector length.
     * 
     * @see Encryption::storeIv()
     *
     * @param string &$data The encrypted string to process.
     * @param string $key   The supplied key to extract the IV with
     *
     * @returns string The initialization vector that was stored
     */
    protected function getIv(&$data, $key) {
        $size = mcrypt_get_iv_size($this->cipher, $this->mode);
        $iv = '';
        for ($i = $size - 1; $i >= 0; $i--) {
            $pos = hexdec($key[$i]);
            $iv = substr($data, $pos, 1) . $iv;
            $data = substr_replace($data, '', $pos, 1);
        }
        if (strlen($iv) != $size) {
            return false;
        }
        return $iv;
    }

    /**
     * Store the Initialization Vector inside the encrypted string.
     *
     * We will need the IV later to decrypt the data, so we need to
     * make it available.  We don't want to just append it, since that
     * could open MITM style attacks on the data.  So we'll hide it 
     * using the key to determine exactly how to hide it.  That way,
     * without knowing the key, it should be impossible to get the IV.
     *
     * @param string $data The data to hide the IV within
     * @param string $iv   The IV to hide
     * @param string $key  The key to use to hide the IV with
     *
     * @returns string The $data parameter with the hidden IV
     */
    protected function storeIv($data, $iv, $key) {
        for ($i = 0; $i < strlen($iv); $i++) {
            $offset = hexdec($key[$i]);
            $data = substr_replace($data, $iv[$i], $offset, 0);
        }
        return $data;
    }

    /**
     * Stretch the key using a simple hmac based stretching algorythm
     *
     * We want to use sha1 here over something stronger since Blowfish
     * expects a key between 4 and 56 bytes.  Sha1 produces a 40 byte
     * hash, so it should be good for these purposes.  This also allows
     * an arbitrary key of any length to be used for encryption.
     *
     * Another benefit of streching the kye is that it actually slows
     * down any potential brute force attacks. 
     *
     * We use 5000 runs for the stretching since it's a good balance
     * between brute force protection and system load.  We could increase
     * this if we were paranoid, but it shouldn't be necessary.
     *
     * @see http://en.wikipedia.org/wiki/Key_stretching
     *
     * @param string $key The key to stretch
     *
     * @returns string A 40 character hex string with the stretched key
     */
    protected function stretch($key) {
        $hash = sha1($key);
        $runs = 0;
        do {
            $hash = hash_hmac('sha1', $hash, $key);
        } while ($runs++ < 5000);
        return $hash;
    }

}

Usage:

$e = new Encryption(MCRYPT_BlOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, 'key');

Then, to decrypt:

$e2 = new Encryption(MCRYPT_BlOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, 'key');

Note that I used $e2 the second time to show you different instances will still properly decrypt the data.

Now, how does it work/why use it over another solution:

  1. Keys

    • The keys are not directly used. Instead, the key is stretched by a 5000 round hmac cycle.

    • The key is used to store the Initialization Vector. That way, without the proper key, you cannot even get the IV. So MITM attacks should be averted.

  2. Data Integrity

    • All stored data is hmac'd to verify that it has not been tampered with prior to encryption. When it's decrypted, it's hmac'ed again (against the key) to determine if it was tampered with and that the correct result was returned.
  3. Encryption:

    • It uses mcrypt to actually perform the encryption. I would suggest using MCRYPT_BLOWFISH cypher and MCRYPT_MODE_CBC for the mode. It's strong enough, and still fairly fast (an encryption and decryption cycle takes about 1/2 second on my machine).

Now, as to point 3 from the first list, what that would give you is a function like this:

function makeKey($userKey, $serverKey, $userSuppliedKey) {
    $key = hash_hmac('sha512', $userKey, $serverKey);
    $key = hash_hmac('sha512', $key, $userSuppliedKey);
    return $key;
}

You could stretch it in the makeKey() function, but since it's going to be stretched later, there's not really a huge point to doing so.

As far as the storage size, it depends on the plain text. Blowfish uses a 8 byte block size, so you'll have:

  • 8 bytes for the initialization vector
  • 40 bytes for the hmac
  • 1 byte for the hmac separator :
  • data length
  • Padding so that (41 + data length) % 8 == 0

So for a 16 character data source, there will be 41 + 16 (57) characters of data to be encrypted. So that means the actual encrypted data size is 64 bytes due to padding. Then add the 8 bytes for the IV and the total stored size is 72 bytes. So there's at best a 49 character overhead, and at worst a 56 character overhead...

I hope that helps...

Written by ircmaxell
This page was build to provide you fast access to the question and the direct accepted answer.
The content is written by members of the stackoverflow.com community.
It is licensed under cc-wiki