TYPO3 API  SVNRelease
class.tx_saltedpasswords_salts_phpass.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2009-2011 Marcus Krause <marcus#exp2009@t3sec.info>
00006 *  All rights reserved
00007 *
00008 *  This script is part of the TYPO3 project. The TYPO3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *  A copy is found in the textfile GPL.txt and important notices to the license
00017 *  from the author is found in LICENSE.txt distributed with these scripts.
00018 *
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 /**
00028  * Contains class "tx_saltedpasswords_salts_phpass"
00029  * that provides PHPass salted hashing.
00030  *
00031  * Derived from Drupal CMS
00032  * original license: GNU General Public License (GPL)
00033  * @see http://drupal.org/node/29706/
00034  *
00035  * Based on the Portable PHP password hashing framework
00036  * original license: Public Domain
00037  * @see http://www.openwall.com/phpass/
00038  *
00039  * $Id: class.tx_saltedpasswords_salts_phpass.php 10120 2011-01-18 20:03:36Z ohader $
00040  */
00041 
00042 
00043 /**
00044  * Class that implements PHPass salted hashing based on Drupal's
00045  * modified Openwall implementation.
00046  *
00047  * PHPass should work on every system.
00048  *
00049  * @author      Marcus Krause <marcus#exp2009@t3sec.info>
00050  *
00051  * @since       2009-09-06
00052  * @package     TYPO3
00053  * @subpackage  tx_saltedpasswords
00054  */
00055 class tx_saltedpasswords_salts_phpass extends tx_saltedpasswords_abstract_salts implements tx_saltedpasswords_salts {
00056     /**
00057      * Keeps a string for mapping an int to the corresponding
00058      * base 64 character.
00059      */
00060     const ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
00061 
00062     /**
00063      * The default log2 number of iterations for password stretching.
00064      */
00065     const HASH_COUNT = 14;
00066 
00067     /**
00068      * The default maximum allowed log2 number of iterations for
00069      * password stretching.
00070      */
00071     const MAX_HASH_COUNT = 24;
00072 
00073     /**
00074      * The default minimum allowed log2 number of iterations for
00075      * password stretching.
00076      */
00077     const MIN_HASH_COUNT = 7;
00078 
00079 
00080     /**
00081      * Keeps log2 number
00082      * of iterations for password stretching.
00083      *
00084      * @var integer
00085      */
00086     static protected $hashCount;
00087 
00088     /**
00089      * Keeps maximum allowed log2 number
00090      * of iterations for password stretching.
00091      *
00092      * @var integer
00093      */
00094     static protected $maxHashCount;
00095 
00096     /**
00097      * Keeps minimum allowed log2 number
00098      * of iterations for password stretching.
00099      *
00100      * @var integer
00101      */
00102     static protected $minHashCount;
00103 
00104     /**
00105      * Keeps length of a PHPass salt in bytes.
00106      *
00107      * @var integer
00108      */
00109     static protected $saltLengthPhpass = 6;
00110 
00111     /**
00112      * Setting string to indicate type of hashing method (PHPass).
00113      *
00114      * @var string
00115      */
00116     static protected $settingPhpass = '$P$';
00117 
00118 
00119     /**
00120      * Method applies settings (prefix, hash count) to a salt.
00121      *
00122      * Overwrites {@link tx_saltedpasswords_salts_md5::applySettingsToSalt()}
00123      * with Blowfish specifics.
00124      *
00125      * @param   string      $salt: a salt to apply setting to
00126      * @return  string      salt with setting
00127      */
00128     protected function applySettingsToSalt($salt) {
00129         $saltWithSettings = $salt;
00130 
00131         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
00132 
00133             // salt without setting
00134         if (strlen($salt) == $reqLenBase64) {
00135                 // We encode the final log2 iteration count in base 64.
00136             $itoa64 = $this->getItoa64();
00137             $saltWithSettings = $this->getSetting() . $itoa64[$this->getHashCount()];
00138 
00139             $saltWithSettings .= $salt;
00140         }
00141 
00142         return $saltWithSettings;
00143     }
00144 
00145     /**
00146      * Method checks if a given plaintext password is correct by comparing it with
00147      * a given salted hashed password.
00148      *
00149      * @param   string      $plainPW: plain-text password to compare with salted hash
00150      * @param   string      $saltedHashPW: salted hash to compare plain-text password with
00151      * @return  boolean     TRUE, if plain-text password matches the salted hash, otherwise FALSE
00152      */
00153     public function checkPassword($plainPW, $saltedHashPW) {
00154         $hash = $this->cryptPassword($plainPW, $saltedHashPW);
00155 
00156         return ($hash && $saltedHashPW === $hash);
00157     }
00158 
00159     /**
00160      * Returns wether all prequesites for the hashing methods are matched
00161      *
00162      * @return  boolean     method available
00163      */
00164     public function isAvailable() {
00165         return TRUE;
00166     }
00167 
00168     /**
00169      * Hashes a password using a secure stretched hash.
00170      *
00171      * By using a salt and repeated hashing the password is "stretched". Its
00172      * security is increased because it becomes much more computationally costly
00173      * for an attacker to try to break the hash by brute-force computation of the
00174      * hashes of a large number of plain-text words or strings to find a match.
00175      *
00176      * @param   string      $password: plain-text password to hash
00177      * @param   string      $setting: an existing hash or the output of getGeneratedSalt()
00178      * @return  mixed       a string containing the hashed password (and salt)
00179      *                      or boolean FALSE on failure.
00180      */
00181     protected function cryptPassword($password, $setting) {
00182         $saltedPW = NULL;
00183 
00184         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
00185 
00186             // Retrieving settings with salt
00187         $setting = substr($setting, 0, strlen($this->getSetting()) + 1 + $reqLenBase64);
00188 
00189         $count_log2 = $this->getCountLog2($setting);
00190 
00191             // Hashes may be imported from elsewhere, so we allow != HASH_COUNT
00192         if ($count_log2 >= $this->getMinHashCount() && $count_log2 <= $this->getMaxHashCount()) {
00193 
00194             $salt = substr($setting, strlen($this->getSetting()) + 1, $reqLenBase64);
00195 
00196                 // We must use md5() or sha1() here since they are the only cryptographic
00197                 // primitives always available in PHP 5. To implement our own low-level
00198                 // cryptographic function in PHP would result in much worse performance and
00199                 // consequently in lower iteration counts and hashes that are quicker to crack
00200                 // (by non-PHP code).
00201             $count = 1 << $count_log2;
00202 
00203             $hash = md5($salt . $password, TRUE);
00204             do {
00205                 $hash = md5($hash . $password, TRUE);
00206             } while (--$count);
00207 
00208             $saltedPW =  $setting . $this->base64Encode($hash, 16);
00209 
00210                 // base64Encode() of a 16 byte MD5 will always be 22 characters.
00211             return (strlen($saltedPW) == 34) ? $saltedPW : FALSE;
00212         }
00213 
00214         return $saltedPW;
00215     }
00216 
00217     /**
00218      * Parses the log2 iteration count from a stored hash or setting string.
00219      *
00220      * @param   string      $setting: complete hash or a hash's setting string or to get log2 iteration count from
00221      * @return  int         used hashcount for given hash string
00222      */
00223     protected function getCountLog2($setting) {
00224         return strpos(
00225             $this->getItoa64(),
00226             $setting[strlen($this->getSetting())]
00227         );
00228     }
00229 
00230     /**
00231      * Generates a random base 64-encoded salt prefixed and suffixed with settings for the hash.
00232      *
00233      * Proper use of salts may defeat a number of attacks, including:
00234      *  - The ability to try candidate passwords against multiple hashes at once.
00235      *  - The ability to use pre-hashed lists of candidate passwords.
00236      *  - The ability to determine whether two users have the same (or different)
00237      *    password without actually having to guess one of the passwords.
00238      *
00239      * @return  string      a character string containing settings and a random salt
00240      */
00241     protected function getGeneratedSalt() {
00242         $randomBytes = t3lib_div::generateRandomBytes($this->getSaltLength());
00243 
00244         return $this->base64Encode($randomBytes, $this->getSaltLength());
00245     }
00246 
00247     /**
00248      * Method returns log2 number of iterations for password stretching.
00249      *
00250      * @return  integer     log2 number of iterations for password stretching
00251      * @see     HASH_COUNT
00252      * @see     $hashCount
00253      * @see     setHashCount()
00254      */
00255     public function getHashCount() {
00256         return isset(self::$hashCount) ? self::$hashCount : self::HASH_COUNT;
00257     }
00258 
00259     /**
00260      * Method creates a salted hash for a given plaintext password
00261      *
00262      * @param   string      $password: plaintext password to create a salted hash from
00263      * @param   string      $salt: optional custom salt with setting to use
00264      * @return  string      salted hashed password
00265      */
00266     public function getHashedPassword($password, $salt = NULL) {
00267         $saltedPW = NULL;
00268 
00269         if (!empty($password)) {
00270             if (empty($salt) || !$this->isValidSalt($salt)) {
00271                 $salt = $this->getGeneratedSalt();
00272             }
00273             $saltedPW = $this->cryptPassword($password, $this->applySettingsToSalt($salt));
00274         }
00275 
00276         return $saltedPW;
00277     }
00278 
00279     /**
00280      * Returns a string for mapping an int to the corresponding base 64 character.
00281      *
00282      * @return  string      string for mapping an int to the corresponding base 64 character
00283      */
00284     protected function getItoa64() {
00285         return self::ITOA64;
00286     }
00287 
00288     /**
00289      * Method returns maximum allowed log2 number of iterations for password stretching.
00290      *
00291      * @return  integer     maximum allowed log2 number of iterations for password stretching
00292      * @see     MAX_HASH_COUNT
00293      * @see     $maxHashCount
00294      * @see     setMaxHashCount()
00295      */
00296     public function getMaxHashCount() {
00297         return isset(self::$maxHashCount) ? self::$maxHashCount : self::MAX_HASH_COUNT;
00298     }
00299 
00300     /**
00301      * Method returns minimum allowed log2 number of iterations for password stretching.
00302      *
00303      * @return  integer     minimum allowed log2 number of iterations for password stretching
00304      * @see     MIN_HASH_COUNT
00305      * @see     $minHashCount
00306      * @see     setMinHashCount()
00307      */
00308     public function getMinHashCount() {
00309         return isset(self::$minHashCount) ? self::$minHashCount : self::MIN_HASH_COUNT;
00310     }
00311 
00312     /**
00313      * Returns length of a Blowfish salt in bytes.
00314      *
00315      * @return  integer     length of a Blowfish salt in bytes
00316      */
00317     public function getSaltLength() {
00318         return self::$saltLengthPhpass;
00319     }
00320 
00321     /**
00322      * Returns setting string of PHPass salted hashes.
00323      *
00324      * @return  string      setting string of PHPass salted hashes
00325      */
00326     public function getSetting() {
00327         return self::$settingPhpass;
00328     }
00329 
00330     /**
00331      * Checks whether a user's hashed password needs to be replaced with a new hash.
00332      *
00333      * This is typically called during the login process when the plain text
00334      * password is available.  A new hash is needed when the desired iteration
00335      * count has changed through a change in the variable $hashCount or
00336      * HASH_COUNT or if the user's password hash was generated in an bulk update
00337      * with class ext_update.
00338      *
00339      * @param   string      $passString  salted hash to check if it needs an update
00340      * @return  boolean     TRUE if salted hash needs an update, otherwise FALSE
00341      */
00342     public function isHashUpdateNeeded($passString) {
00343             // Check whether this was an updated password.
00344         if ((strncmp($passString, '$P$', 3)) || (strlen($passString) != 34)) {
00345             return TRUE;
00346         }
00347             // Check whether the iteration count used differs from the standard number.
00348         return ($this->getCountLog2($passString) < $this->getHashCount());
00349     }
00350 
00351     /**
00352      * Method determines if a given string is a valid salt.
00353      *
00354      * @param   string      $salt:  string to check
00355      * @return  boolean     TRUE if it's valid salt, otherwise FALSE
00356      */
00357     public function isValidSalt($salt) {
00358         $isValid = $skip = FALSE;
00359 
00360         $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
00361 
00362         if (strlen($salt) >= $reqLenBase64) {
00363                 // salt with prefixed setting
00364             if (!strncmp('$', $salt, 1)) {
00365                 if (!strncmp($this->getSetting(), $salt, strlen($this->getSetting()))) {
00366                     $isValid = TRUE;
00367                     $salt = substr($salt, strrpos($salt, '$') + 2);
00368                 } else {
00369                     $skip = TRUE;
00370                 }
00371             }
00372 
00373                 // checking base64 characters
00374             if (!$skip && (strlen($salt) >= $reqLenBase64)) {
00375                 if (preg_match('/^[' . preg_quote($this->getItoa64(),'/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
00376                     $isValid = TRUE;
00377                 }
00378             }
00379         }
00380 
00381         return $isValid;
00382     }
00383 
00384     /**
00385      * Method determines if a given string is a valid salted hashed password.
00386      *
00387      * @param   string      $saltedPW: string to check
00388      * @return  boolean     TRUE if it's valid salted hashed password, otherwise FALSE
00389      */
00390     public function isValidSaltedPW($saltedPW) {
00391         $isValid = FALSE;
00392 
00393         $isValid = (!strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()))) ? TRUE : FALSE;
00394         if ($isValid) {
00395             $isValid = $this->isValidSalt($saltedPW);
00396         }
00397 
00398         return $isValid;
00399     }
00400 
00401     /**
00402      * Method sets log2 number of iterations for password stretching.
00403      *
00404      * @param   integer     $hashCount: log2 number of iterations for password stretching to set
00405      * @see     HASH_COUNT
00406      * @see     $hashCount
00407      * @see     getHashCount()
00408      */
00409     public function setHashCount($hashCount = NULL) {
00410         self::$hashCount = !is_NULL($hashCount) && is_int($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
00411     }
00412 
00413     /**
00414      * Method sets maximum allowed log2 number of iterations for password stretching.
00415      *
00416      * @param   integer     $maxHashCount: maximum allowed log2 number of iterations for password stretching to set
00417      * @see     MAX_HASH_COUNT
00418      * @see     $maxHashCount
00419      * @see     getMaxHashCount()
00420      */
00421     public function setMaxHashCount($maxHashCount = NULL) {
00422         self::$maxHashCount = !is_NULL($maxHashCount) && is_int($maxHashCount) ? $maxHashCount : self::MAX_HASH_COUNT;
00423     }
00424 
00425     /**
00426      * Method sets minimum allowed log2 number of iterations for password stretching.
00427      *
00428      * @param   integer     $minHashCount  minimum allowed log2 number of iterations for password stretching to set
00429      * @see     MIN_HASH_COUNT
00430      * @see     $minHashCount
00431      * @see     getMinHashCount()
00432      */
00433     public function setMinHashCount($minHashCount = NULL) {
00434         self::$minHashCount = !is_NULL($minHashCount) && is_int($minHashCount) ? $minHashCount : self::MIN_HASH_COUNT;
00435     }
00436 }
00437 
00438 
00439 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/classes/salts/class.tx_saltedpasswords_salts_phpass.php'])) {
00440     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/classes/salts/class.tx_saltedpasswords_salts_phpass.php']);
00441 }
00442 ?>