openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

Password.class.php (7088B)


      1 <?php
      2 namespace security;
      3 
      4 
      5 
      6 use cms\base\Configuration;
      7 
      8 /**
      9  * Security functions for passwords.
     10  * 
     11  * @author Jan dankert
     12  *
     13  */
     14 class Password
     15 {
     16     /**
     17      * yes, we are supporting PLAIN passwords. Why?
     18      * Normally, there are not used, but in developing situations this is useful.
     19      */
     20     const ALGO_PLAIN             = 0;
     21     const ALGO_CRYPT             = 1;
     22     const ALGO_MD5               = 2;
     23     const ALGO_PHP_PASSWORD_HASH = 3;
     24     const ALGO_SHA1              = 4;
     25 
     26     /**
     27 	 * Detects the best available algorhythm for password hashing.
     28 	 */
     29 	static public function bestAlgoAvailable()
     30 	{
     31 		if	( function_exists('password_hash') )
     32 		{
     33 		    // Use BCRYPT, this is available since PHP 5.5 and is safe for now.
     34 			return self::ALGO_PHP_PASSWORD_HASH;
     35 		}
     36 		elseif	( function_exists('crypt') && defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH == 1 )
     37 		{
     38 		    // see https://en.wikipedia.org/wiki/Blowfish_(cipher)
     39             // BLOWFISH
     40 			return self::ALGO_CRYPT;
     41 		}
     42 		elseif	( function_exists('sha1') )
     43 		{
     44             // see https://en.wikipedia.org/wiki/SHA-1
     45             // should not be used because of some security issues.
     46 			return self::ALGO_SHA1;
     47 		}
     48 		elseif	( function_exists('md5') )
     49 		{
     50             // see https://en.wikipedia.org/wiki/MD5
     51             // should not be used because of some security issues.
     52 			return self::ALGO_MD5;
     53 		}
     54 		else
     55 		{
     56 		    // This should never happen ;)
     57 			return self::ALGO_PLAIN;
     58 		}
     59 	}
     60 
     61 
     62 	
     63 	/**
     64 	 * Hash the password.
     65      *
     66 	 * @param $password string The password to hash
     67 	 * @param $algo int Hashing algorhythm
     68 	 * @param $cost cost factor: An integer between 4 and 31.
     69 	 */
     70 	static public function hash( $password,$algo,$cost=10 )
     71 	{
     72 		switch( $algo )
     73 		{
     74 			case self::ALGO_PHP_PASSWORD_HASH:
     75 
     76 			    return password_hash( $password, PASSWORD_BCRYPT,array('cost'=>$cost) );
     77 				
     78 			case self::ALGO_CRYPT:
     79 
     80 				$salt  = Password::randomHexString(10); // this should be cryptographically safe.
     81 
     82                 // see https://www.php.net/security/crypt_blowfish.php
     83 				if	( version_compare(PHP_VERSION, '5.3.7') >= 0 )
     84 					$algo = '2y'; // BLOWFISH
     85 				else
     86 					$algo = '2a'; // "old" BLOWFISH, but no problem if using PHP >= 5.3.7
     87 				
     88 				// cost factor should be between '04' and '31'.
     89 				$cost = max(min($cost,31),4); 
     90 				$cost = str_pad($cost, 2, '0', STR_PAD_LEFT);
     91 				
     92 				return crypt($password,'$'.$algo.'$'.$cost.'$'.$salt.'$');
     93 				
     94 			case self::ALGO_MD5:
     95 				return md5($password); // ooold.
     96 				
     97 			case self::ALGO_SHA1:
     98 				return sha1($password); //
     99 				
    100 			case self::ALGO_PLAIN:
    101 				return $password; // you want it, you get it.
    102 		}
    103 	}
    104 	
    105 	/**
    106 	 * Prüft das Kennwort gegen einen gespeicherten Hashwert.
    107 	 * 
    108 	 * @param String $password Passwort
    109 	 * @param String $hash Hash
    110 	 * @return boolean true, falls das Passwort dem Hashwert entspricht.
    111 	 */
    112 	static public function check( $password,$hash,$algo )
    113 	{
    114 		switch( $algo )
    115 		{
    116 			case self::ALGO_PHP_PASSWORD_HASH:
    117 			    // This is 'timing attack safe' as the documentation says.
    118 				return password_verify($password,$hash);
    119 				
    120 			case self::ALGO_CRYPT:
    121 		
    122 				if	( function_exists('crypt') )
    123 				{
    124 					// Workaround: Die Spalte 'password' war frueher nur 50 Stellen lang, daher
    125 					// wird der mit crypt() erzeugte Hash auf die Länge des gespeicherten Hashes
    126 					// gekürzt. Mit aktuellen Versionen gespeicherte Hashes haben die volle Länge.
    127 					return Password::equals( $hash, substr(crypt($password,$hash),0,strlen($hash)) );
    128 				}
    129 				else
    130 				{
    131 					throw new LogicException("Modular crypt format is not supported by this PHP ".PHP_VERSION."  (no function 'crypt()')");
    132 				}
    133 
    134 			case self::ALGO_SHA1:
    135 			    return Password::equals( $hash, sha1($password) );
    136 				
    137 			case self::ALGO_MD5:
    138 			    return Password::equals( $hash, md5($password) );
    139 				
    140 			case self::ALGO_PLAIN:
    141 			    return Password::equals( $hash, $password );
    142 		}
    143 	}
    144 	
    145 	
    146 	/**
    147 	 * Creates cryptographic safe random bytes in HEX format.
    148 	 * @param int $bytesCount
    149 	 * @return string HEX
    150 	 */
    151 	static public function randomHexString( $bytesCount )
    152 	{
    153 		return bin2hex( Password::randomBytes($bytesCount) );
    154 	}
    155 	
    156 
    157 	/**
    158 	 * Creates a cryptographic safe number.
    159 	 * @param int $bytesCount
    160 	 * @return int
    161 	 */
    162 	static public function randomNumber( $bytesCount )
    163 	{
    164 		// With '@' we reject the message "Invalid characters passed" since PHP 7.4
    165    		return @bindec( Password::randomBytes($bytesCount) );
    166 	}
    167 	
    168 
    169 	/**
    170 	 * Creates cryptographic safe random bytes.
    171 	 * @param int $bytesCount
    172 	 * @return string Binary bytes
    173 	 */
    174 	static public function randomBytes( $bytesCount )
    175 	{
    176 		if	( function_exists('random_bytes') )
    177 		{
    178 			return random_bytes($bytesCount);
    179 		}
    180 		elseif	( function_exists('openssl_random_pseudo_bytes') )
    181 		{
    182 			return openssl_random_pseudo_bytes($bytesCount);
    183 		}
    184 		else
    185 		{
    186 			// This fallback is NOT cryptographic safe!
    187 			$buf = '';
    188     		for ($i = 0; $i < $length; ++$i)
    189     		{
    190 		        $buf .= chr(mt_rand(0, 255));
    191 		    }
    192     		return $buf;
    193 		}
    194 	}
    195 	
    196 
    197 	/**
    198 	 * Time-safe Compare of 2 strings.
    199 	 * 
    200 	 * @param String $known
    201 	 * @param String $user
    202 	 * @return boolean true, if equal
    203 	 */
    204 	static private function equals( $known, $user )
    205 	{
    206         if( function_exists('hash_equals'))
    207         {
    208             return hash_equals($known, $user);
    209         }
    210         else
    211         {
    212             if  ( strlen($known) != strlen($user) )
    213             {
    214                 return false;
    215             }
    216             else
    217             {
    218                 $res = $known ^ $user;
    219                 $ret = 0;
    220                 for($i = strlen($res) - 1; $i >= 0; $i--) $ret |= ord($res[$i]);
    221                 return !$ret;
    222             }
    223         }
    224 	}
    225 	
    226 
    227 	/**
    228 	 * Cryptographic delay of execution.
    229 	 * Delay is from 0 to 168 milliseconds, Steps of 10 nanoseconds(!), which would be very heavy to attack over a network.
    230 	 */
    231 	static public function delay()
    232 	{
    233 	    time_nanosleep(0, Password::randomNumber(3)*10); // delay: 0-167772150ns (= 0-~168ms)  
    234 	}
    235 
    236 
    237 	/**
    238 	 * Creates a new, pronounceable password.
    239 	 *
    240 	 * Inspired by http://www.phpbuilder.com/annotate/message.php3?id=1014451
    241 	 *
    242 	 * @return String a random password
    243 	 */
    244 	public static function createPassword()
    245 	{
    246 		$passwordConfig = Configuration::subset('security')->subset('password');
    247 
    248 		$pw = '';
    249 		$c  = 'bcdfghjklmnprstvwz'; // consonants except hard to speak ones
    250 		$v  = 'aeiou';              // vowels
    251 		$a  = $c.$v.'123456789';    // both (plus numbers except zero)
    252 
    253 		//use two syllables...
    254 		for ( $i=0; $i < intval($passwordConfig->get('generated_length',16))/3; $i++ )
    255 		{
    256 			$pw .= $c[rand(0, strlen($c)-1)];
    257 			$pw .= $v[rand(0, strlen($v)-1)];
    258 			$pw .= $a[rand(0, strlen($a)-1)];
    259 		}
    260 
    261 		return $pw;
    262 	}
    263 
    264 
    265 
    266 	/**
    267 	 * Pepper the password.
    268 	 *
    269 	 * Siehe http://de.wikipedia.org/wiki/Salt_%28Kryptologie%29#Pfeffer
    270 	 * für weitere Informationen.
    271 	 *
    272 	 * @param $pass string password
    273 	 * @return string peppered password
    274 	 */
    275 	public static function pepperPassword( $pass )
    276 	{
    277 		$salt = Configuration::Conf()->subset('security')->subset('password')->get('pepper');
    278 
    279 		return $salt.$pass;
    280 	}
    281 
    282 
    283 
    284 }