Question

I have decided recently to use a more secure encrytion for my password. I have no problem encrypting my password, after calling mc_encrypt($encrypt) the method returns an encrypted password.

When doing decryption by calling mc_decrypt($decrypt), the method returns false. As you can see in the mc_decrypt($decrypt) method, there is an if statement near the bottom. I cannot get the if statement to pass. Does anyone know what I can change to get $calcmac!==$mac to return true? Thanks

<?php

    class Encrypt {
    public $encryptionKey = 'xxxxxxx';

    public function __construct() {
    define('ENCRYPTION_KEY', $this->encryptionKey);
    }

    // Encrypt Function
    public function mc_encrypt($encrypt){
        $encrypt = serialize($encrypt);
        $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);
        $key = pack('H*', $this->encryptionKey);
        $mac = hash_hmac('sha256', $encrypt, substr(bin2hex($key), -32));
        $passcrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $encrypt.$mac, MCRYPT_MODE_CBC, $iv);
        $encoded = base64_encode($passcrypt).'|'.base64_encode($iv);
        return $encoded;
    }

    // Decrypt Function
    public function mc_decrypt($decrypt){
        $decrypt = explode('|', $decrypt);
        $decoded = base64_decode($decrypt[0]);
        $iv = base64_decode($decrypt[1]);
        $key = pack('H*', $this->encryptionKey);
        $decrypted = trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $decoded, MCRYPT_MODE_CBC, $iv));
        $mac = substr($decrypted, -64);
        $decrypted = substr($decrypted, 0, -64);
        $calcmac = hash_hmac('sha256', $decrypted, substr(bin2hex($key), -32));
        if($calcmac!==$mac){ return false; }
        $decrypted = unserialize($decrypted);
        return $decrypted;
    }

    }

    ?>
Was it helpful?

Solution

Sorry, encrypting passwords with a secret stored on the server isn't secure: If an intruder breaks into your codebase, intruder can retrieve each and any password using your codebase and the stored password (somewhere in code or your persistent store).

OWASP, the Online Web Application Security Project, provides well prepared documents: Password Storage Cheat Sheet, Authentication Cheat Sheet and PHP Security Cheat Sheet. Have a look!

The way to go is a salted hash.

How to handle newly created passwords

  • If you create a new password, compute hash = HashFunction( password, salt ) with a random salt
  • Save hash and salt in your database along to the user's ID

How to verify password

  • Locate the userID's record in your database
  • retrieve hash and salt from the record
  • Based on the password, that user entered to log in, compute hash = HashFunction( enteredPassword, salt )
  • Finally, verify if the hash retrieved from store is identical to the one computed.

Why use a hash operation?

A hash operation is a so called trapdoor function: While you can compute the function easily, it's hard to compute the reverse function. Corollary: It's easy to compute a password-hash from a password, but it's hard to compute the password from the password-hash.

PHP's hash functions

These days, PHP's PBKDF2 is first choice for password hashes [Wikipedia on PBKDF2].

If your PHP installation is too old, even salted hashes with md5() are better than two-encrypted password. But only in case definitely nothing else is available!

Sample of PBKDF2 usage

function getHashAndSaltFromString( $password ) {

   // choose a sufficiently long number of iterations 
   // ... to make the operation COSTLY
   $iterations = 1000;

   // Generate a random IV using mcrypt_create_iv(),
   // openssl_random_pseudo_bytes() or another suitable source of randomness
   $salt = mcrypt_create_iv(16, MCRYPT_DEV_RANDOM);

   $hash = hash_pbkdf2("sha256", $password, $salt, $iterations, 20);

   return array( $hash, $salt );

}

Beautiful side effect of hash usage

Many websites do restrict the length of passwords to a certain length - quite likely due to the length of the underlying persistent storage [= length of field in a database table].

If you use the hash-based password storage technique, your users might use passwords of arbitrary length!

Since the hash is of constant length and you only persist password and salt, the length restriction is superfluous. Support long passwords in your web-app!

At an extreme case, you could even allow users to upload a file - e.g. a picture of their home - as a credential [=password].

Side note on random sources in PHP's MCRYPT functions

Note, that PHP does provide two sources of randomness MCRYPT_DEV_RANDOM and MCRYPT_DEV_URANDOM.

  • MCRYPT_DEV_RANDOM gets its randomness from /dev/random
  • MCRYPT_DEV_URANDOM gets its randomness from /dev/urandom

/dev/urandom provides random data immediately and non-blocking each time you query it, /dev/random might block (take some time to return).

Therefore, at first sight, /dev/urandom and MCRYPT_DEV_URANDOM might be better suited for random number generation. In fact, it is not!

/dev/random might block request up to a point of time, at which sufficiently much entropy has been collected. Thus /dev/random and MCRYPT_DEV_RANDOM effectively collects randomness.

If you need to do strong crypto operations, use MCRYPT_DEV_RANDOM or /dev/random.

OTHER TIPS

You shouldn't rely on two-way encryption for passwords. If someone can get the cyphertext, they can usually also get the key to decrypt them.

Instead, you should use hashing or key derivation like blowfish or pbkdf2, those are one-way functions that are designed to be hard to crack.

Please, never ever encrypt passwords this way.

If you are using PHP >= 5.3.7, you should use the password_comp library which was written by guys involved in PHP and it utilizes BCRYPT which is the strongest algorithm available for PHP as of yet.

It's very simple and easy to use.

https://github.com/ircmaxell/password_compat

Hash the password simply

 $hash = password_hash($password, PASSWORD_BCRYPT);

Verify the given password against the stored password hash

if (password_verify($password, $hash)) {
    /* Valid */
} else {
    /* Invalid */
}

Pretty simple stuff, like they say no need to rewrite was has already been written especially when it comes to security and written by the experts.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top