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$algoarray $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 || $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 (== 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 1);
  128.             $buffer_valid false;
  129.             if (function_exists('mcrypt_create_iv')) {
  130.                 $buffer mcrypt_create_iv($raw_lengthMCRYPT_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[$ichr(mt_rand(0255));
  160.                     else {
  161.                         $buffer .= chr(mt_rand(0255));
  162.                     }
  163.                 }
  164.             }
  165.             $salt str_replace('+''.'base64_encode($buffer));
  166.  
  167.         }
  168.         $salt substr($salt0$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($hash04== _PASSWORD_CRYPT_PROLOG && strlen($hash== 60{
  204.             $return['algo'PASSWORD_BCRYPT;
  205.             $return['algoName''bcrypt';
  206.             list($costsscanf($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$algoarray $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 Thu, 19 Sep 2013 00:09:01 +0200 by phpDocumentor 1.4.3