Search code examples
phpsecuritypasswordsmd5imap

How to store a password when you need the actual password text


My database stores user's login passwords in MD5, so when they login it compares their typed in password converted to MD5 vs the MD5 password in the database.

I'm implementing functionality where the application can connect to their email account using IMAP. So I need to store the user's email account password in the database, but as far as I'm aware I can't use MD5 because I need to actually end up with their password as plain text to use to connect via an IMAP connection.

What would be a solution to store their password securely in the database, but be able to retrieve it and convert it into plain text in a script?


Solution

  • First off, MD5 is not a secure way to store passwords. Take a look at the password_hash function and bcrypt for a more modern and secure solution. See also: How do you use bcrypt for hashing passwords in PHP?

    As for the IMAP password, you do not need hashing as those are "one-way". You need an encryption algorithm.

    One of the better options is to use openssl_encrypt() and a secure key stored on the server. This is the approach Laravel takes for example.

    Take a look at this as an example: https://github.com/laravel/framework/blob/5.2/src/Illuminate/Encryption/Encrypter.php#L62

    /**
     * Encrypt the given value.
     *
     * @param  string  $value
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\EncryptException
     */
    public function encrypt($value)
    {
        $iv = random_bytes($this->getIvSize());
        $value = \openssl_encrypt(serialize($value), $this->cipher, $this->key, 0, $iv);
        if ($value === false) {
            throw new EncryptException('Could not encrypt the data.');
        }
        // Once we have the encrypted value we will go ahead base64_encode the input
        // vector and create the MAC for the encrypted value so we can verify its
        // authenticity. Then, we'll JSON encode the data in a "payload" array.
        $mac = $this->hash($iv = base64_encode($iv), $value);
        $json = json_encode(compact('iv', 'value', 'mac'));
        if (! is_string($json)) {
            throw new EncryptException('Could not encrypt the data.');
        }
        return base64_encode($json);
    }
    /**
     * Decrypt the given value.
     *
     * @param  string  $payload
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\DecryptException
     */
    public function decrypt($payload)
    {
        $payload = $this->getJsonPayload($payload);
        $iv = base64_decode($payload['iv']);
        $decrypted = \openssl_decrypt($payload['value'], $this->cipher, $this->key, 0, $iv);
        if ($decrypted === false) {
            throw new DecryptException('Could not decrypt the data.');
        }
        return unserialize($decrypted);
    }
    /**
     * Get the IV size for the cipher.
     *
     * @return int
     */
    protected function getIvSize()
    {
        return 16;
    }
    

    Obviously, that Laravel code relies on other parts of Laravel, but as an example, this is sufficient. Once you have the encrypted value, you can store it in your database.

    Remember security is only as strong as the weakest link -- so make sure your database credentials are secure and your database user has the minimum amount of permissions for your application to function.