/** * Validate a user-supplied password against a stored password generated * using the phpsecPw::hash() method. * * @param string $password * The password supplied by the user in the login form. * * @param string $dbPassword * The json string fetched from the database, in the exact format * as created by phpsecPw::hash(). * * @return boolean * True on password match, false otherwise. */ public static function check($password, $dbPassword) { /** * Unserialize registered password array and validate it to ensure * we got a valid array. */ $data = json_decode($dbPassword, true); $dataStructure = array('hash' => true, 'salt' => true, 'algo' => true); /* Check structure of array. */ if ($data !== null && phpsec::arrayCheck($data, $dataStructure)) { /* Try to Base64 decode the salt. base64_decode() will return false * if the string passed is not Base64 encoded. This way we can separate * binary salts from the old type of salts. */ $decodedSalt = base64_decode($data['salt'], true); if ($decodedSalt !== false) { /* The salt was Base64 encoded. Use the decoded version. */ $data['salt'] = $decodedSalt; } /** * We do a switch on the 6 first characters on the used hashing method. * This way we are able to catch when pbkdf2 is used, since this has * it's iteration count, derived key length and PRF attached to it: * "pbkdf2:iteration count:derived key length:PRF" */ switch (substr($data['algo'], 0, 6)) { case self::phpsecPw_PBKDF2: /* As described above, we need to seperate out the iteration count * and derived key length. */ list($method, $iterationCount, $dkLen, $prf) = explode(':', $data['algo']); /* Just to make sure anything fishy isn't going on. */ if (!is_numeric($iterationCount) || !is_numeric($dkLen)) { return false; } /* Create a new derived key, with the iteration count and derived key length * that were used when generating the original dk. */ $dk = phpsecCrypt::pbkdf2($password, $data['salt'], $iterationCount, $dkLen, $prf); /* Check the new dk against the old base64 encoded dk. */ if ($dk === base64_decode($data['hash'])) { return true; } break; default: /* If not pbkdf2, we assume normal hash. */ $pwInjected = self::inject($password, $data['salt']); /* Create a hash and see if it matches. */ if (hash($data['algo'], $pwInjected) == $data['hash']) { return true; } } } else { /* Invalid array supplied. */ phpsec::error('Invalid data supplied. Expected serialized array as returned by pwHash()'); } return false; }
/** * Strip PKCS7 padding and decrypt * data encrypted by encrypt(). * * @param string $data * JSON string containing the encrypted data and meta information in the * excact format as returned by encrypt(). * * @return mixed * Decrypted data in it's original form. */ public static function decrypt($data, $key) { /* Decode the JSON string */ $data = json_decode($data, true); $dataStructure = array('algo' => true, 'mode' => true, 'iv' => true, 'cdata' => true, 'mac' => true); if ($data === null || phpsec::arrayCheck($data, $dataStructure) !== true) { phpsec::error('Invalid data passed to decrypt()'); return false; } /* Everything looks good so far. Let's continue.*/ $td = mcrypt_module_open($data['algo'], '', $data['mode'], ''); $block = mcrypt_enc_get_block_size($td); /* Check MAC. */ if (base64_decode($data['mac']) != self::pbkdf2($data['cdata'], $key, 1000, 32)) { phpsec::error('Message authentication code invalid'); return false; } /* Init mcrypt. */ mcrypt_generic_init($td, $key, base64_decode($data['iv'])); $decrypted = rtrim(mdecrypt_generic($td, base64_decode(self::stripPadding($block, $data['cdata'])))); /* Close up. */ mcrypt_generic_deinit($td); mcrypt_module_close($td); /*Return decrypted data. */ return unserialize($decrypted); }