Source for file password.php

Documentation is available at password.php

  1. <?php
  2. /**
  3.  * A Compatibility library with PHP 5.5's simplified password hashing API.
  4.  *
  5.  * @author Anthony Ferrara <ircmaxell@php.net>
  6.  * @contributor Laurent Jouanneau <laurent@jelix.org>
  7.  * @license http://www.opensource.org/licenses/mit-license.html MIT License
  8.  * @copyright 2012 The Authors
  9.  */
  10.  
  11.  /**
  12.   * function to check if the password API can be used
  13.   * In some PHP version ( <5.3.7), crypt() with blowfish is vulnerable.
  14.   * But this issue has been fixed on some older PHP version (php 5.3.3 for most of them) in some
  15.   * distro, like Debian squeeze.
  16.   * @see http://www.php.net/security/crypt_blowfish.php
  17.   */
  18. function can_use_password_API () {
  19.     if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
  20.         if (!defined('_PASSWORD_CRYPT_HASH_FORMAT'))
  21.             define('_PASSWORD_CRYPT_HASH_FORMAT', '$2y$%02d$');
  22.         if (!defined('_PASSWORD_CRYPT_PROLOG'))
  23.             define('_PASSWORD_CRYPT_PROLOG', '$2y$');
  24.         return true;
  25.     }
  26.     if (version_compare(PHP_VERSION, '5.3.3', '<')) {
  27.         return false;
  28.     }
  29.     // On debian squeeze, crypt() has been fixed in PHP 5.3.3
  30.     // http://security-tracker.debian.org/tracker/CVE-2011-2483
  31.     // so we can use crypt() securely with $2a$ ($2y$ is not available)
  32.     if (preg_match('/squeeze(\d+)$/', PHP_VERSION, $m)) {
  33.         if (intval($m[1]) >= 4) {
  34.             if (!defined('_PASSWORD_CRYPT_HASH_FORMAT'))
  35.                 define('_PASSWORD_CRYPT_HASH_FORMAT', '$2a$%02d$');
  36.             if (!defined('_PASSWORD_CRYPT_PROLOG'))
  37.                 define('_PASSWORD_CRYPT_PROLOG', '$2a$');
  38.             return true;
  39.         }
  40.     }
  41.     //FIXME crypt() in PHP 5.3.3 is fixed also on other distro like RedHat.
  42.     // however I don't know if it supports 2y, and how does PHP_VERSION look like
  43.     return false;
  44. }
  45.  
  46.  
  47.  
  48. if (!can_use_password_API()) {
  49.     trigger_error("The Password Compatibility Library requires PHP >= 5.3.7 or PHP >= 5.3.3-7+squeeze4 on debian", E_USER_WARNING);
  50.     // Prevent defining the functions
  51.     return;
  52. }
  53.  
  54. if (!defined('PASSWORD_BCRYPT')) {
  55.  
  56.     define('PASSWORD_BCRYPT', 1);
  57.     define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
  58.  
  59.     /**
  60.      * Hash the password using the specified algorithm
  61.      *
  62.      * @param string $password The password to hash
  63.      * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
  64.      * @param array  $options  The options for the algorithm to use
  65.      *
  66.      * @returns string|false The hashed password, or false on error.
  67.      */
  68.     function password_hash($password, $algo, array $options = array()) {
  69.         if (!function_exists('crypt')) {
  70.             trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
  71.             return null;
  72.         }
  73.         if (!is_string($password)) {
  74.             trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
  75.             return null;
  76.         }
  77.         if (!is_int($algo)) {
  78.             trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
  79.             return null;
  80.         }
  81.         switch ($algo) {
  82.             case PASSWORD_BCRYPT:
  83.                 // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
  84.                 $cost = 10;
  85.                 if (isset($options['cost'])) {
  86.                     $cost = $options['cost'];
  87.                     if ($cost < 4 || $cost > 31) {
  88.                         trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
  89.                         return null;
  90.                     }
  91.                 }
  92.                 $required_salt_len = 22;
  93.                 $hash_format = sprintf(_PASSWORD_CRYPT_HASH_FORMAT, $cost);
  94.                 break;
  95.             default:
  96.                 trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
  97.                 return null;
  98.         }
  99.         if (isset($options['salt'])) {
  100.             switch (gettype($options['salt'])) {
  101.                 case 'NULL':
  102.                 case 'boolean':
  103.                 case 'integer':
  104.                 case 'double':
  105.                 case 'string':
  106.                     $salt = (string) $options['salt'];
  107.                     break;
  108.                 case 'object':
  109.                     if (method_exists($options['salt'], '__tostring')) {
  110.                         $salt = (string) $options['salt'];
  111.                         break;
  112.                     }
  113.                 case 'array':
  114.                 case 'resource':
  115.                 default:
  116.                     trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
  117.                     return null;
  118.             }
  119.             if (strlen($salt) < $required_salt_len) {
  120.                 trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
  121.                 return null;
  122.             } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
  123.                 $salt = str_replace('+', '.', base64_encode($salt));
  124.             }
  125.         } else {
  126.             $buffer = '';
  127.             $raw_length = (int) ($required_salt_len * 3 / 4 + 1);
  128.             $buffer_valid = false;
  129.             if (function_exists('mcrypt_create_iv')) {
  130.                 $buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM);
  131.                 if ($buffer) {
  132.                     $buffer_valid = true;
  133.                 }
  134.             }
  135.             if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
  136.                 $buffer = openssl_random_pseudo_bytes($raw_length);
  137.                 if ($buffer) {
  138.                     $buffer_valid = true;
  139.                 }
  140.             }
  141.             if (!$buffer_valid && file_exists('/dev/urandom')) {
  142.                 $f = @fopen('/dev/urandom', 'r');
  143.                 if ($f) {
  144.                     $read = strlen($buffer);
  145.                     while ($read < $raw_length) {
  146.                         $buffer .= fread($f, $raw_length - $read);
  147.                         $read = strlen($buffer);
  148.                     }
  149.                     fclose($f);
  150.                     if ($read >= $raw_length) {
  151.                         $buffer_valid = true;
  152.                     }
  153.                 }
  154.             }
  155.             if (!$buffer_valid || strlen($buffer) < $raw_length) {
  156.                 $bl = strlen($buffer);
  157.                 for ($i = 0; $i < $raw_length; $i++) {
  158.                     if ($i < $bl) {
  159.                         $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
  160.                     } else {
  161.                         $buffer .= chr(mt_rand(0, 255));
  162.                     }
  163.                 }
  164.             }
  165.             $salt = str_replace('+', '.', base64_encode($buffer));
  166.  
  167.         }
  168.         $salt = substr($salt, 0, $required_salt_len);
  169.  
  170.         $hash = $hash_format . $salt;
  171.  
  172.         $ret = crypt($password, $hash);
  173.  
  174.         if (!is_string($ret) || strlen($ret) <= 13) {
  175.             return false;
  176.         }
  177.  
  178.         return $ret;
  179.     }
  180.  
  181.     /**
  182.      * Get information about the password hash. Returns an array of the information
  183.      * that was used to generate the password hash.
  184.      *
  185.      * array(
  186.      *    'algo' => 1,
  187.      *    'algoName' => 'bcrypt',
  188.      *    'options' => array(
  189.      *        'cost' => 10,
  190.      *    ),
  191.      * )
  192.      *
  193.      * @param string $hash The password hash to extract info from
  194.      *
  195.      * @return array The array of information about the hash.
  196.      */
  197.     function password_get_info($hash) {
  198.         $return = array(
  199.             'algo' => 0,
  200.             'algoName' => 'unknown',
  201.             'options' => array(),
  202.         );
  203.         if (substr($hash, 0, 4) == _PASSWORD_CRYPT_PROLOG && strlen($hash) == 60) {
  204.             $return['algo'] = PASSWORD_BCRYPT;
  205.             $return['algoName'] = 'bcrypt';
  206.             list($cost) = sscanf($hash, _PASSWORD_CRYPT_HASH_FORMAT);
  207.             $return['options']['cost'] = $cost;
  208.         }
  209.         return $return;
  210.     }
  211.  
  212.     /**
  213.      * Determine if the password hash needs to be rehashed according to the options provided
  214.      *
  215.      * If the answer is true, after validating the password using password_verify, rehash it.
  216.      *
  217.      * @param string $hash    The hash to test
  218.      * @param int    $algo    The algorithm used for new password hashes
  219.      * @param array  $options The options array passed to password_hash
  220.      *
  221.      * @return boolean True if the password needs to be rehashed.
  222.      */
  223.     function password_needs_rehash($hash, $algo, array $options = array()) {
  224.         $info = password_get_info($hash);
  225.         if ($info['algo'] != $algo) {
  226.             return true;
  227.         }
  228.         switch ($algo) {
  229.             case PASSWORD_BCRYPT:
  230.                 $cost = isset($options['cost']) ? $options['cost'] : 10;
  231.                 if ($cost != $info['options']['cost']) {
  232.                     return true;
  233.                 }
  234.                 break;
  235.         }
  236.         return false;
  237.     }
  238.  
  239.     /**
  240.      * Verify a password against a hash using a timing attack resistant approach
  241.      *
  242.      * @param string $password The password to verify
  243.      * @param string $hash     The hash to verify against
  244.      *
  245.      * @return boolean If the password matches the hash
  246.      */
  247.     function password_verify($password, $hash) {
  248.         if (!function_exists('crypt')) {
  249.             trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
  250.             return false;
  251.         }
  252.         $ret = crypt($password, $hash);
  253.         if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
  254.             return false;
  255.         }
  256.  
  257.         $status = 0;
  258.         for ($i = 0; $i < strlen($ret); $i++) {
  259.             $status |= (ord($ret[$i]) ^ ord($hash[$i]));
  260.         }
  261.  
  262.         return $status === 0;
  263.     }
  264. }

Documentation generated on Wed, 24 Sep 2014 22:04:01 +0200 by phpDocumentor 1.4.3