openrat-cms

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

commit c03050961b0554c8b6b18b2188093613013b8d7e
parent 1d237163c661fed923dfeaa4811149b8b862a96d
Author: Jan Dankert <devnull@localhost>
Date:   Mon,  6 Nov 2017 22:32:18 +0100

Algorithmen 'sha1' und PHP-Password-Hashing (ab PHP 5.5) unterstuetzen.

Diffstat:
util/Password.class.php | 133++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 109 insertions(+), 24 deletions(-)

diff --git a/util/Password.class.php b/util/Password.class.php @@ -3,13 +3,12 @@ define('OR_PASSWORD_ALGO_PLAIN',0); define('OR_PASSWORD_ALGO_CRYPT',1); define('OR_PASSWORD_ALGO_MD5' ,2); +define('OR_PASSWORD_ALGO_PHP_PASSWORD_HASH',3); +define('OR_PASSWORD_ALGO_SHA1' ,4); /** - * Hashfunktion für Passwörter. - * - * Als Hashfunktion wird Bcrypt verwendet. Falls Bcrypt nicht zur - * Verfügung steht, erfolgt ein Fallback auf MD5. + * Sicherheitsfunktionen für Passwörter. * * @author dankert * @@ -21,10 +20,18 @@ class Password */ static public function bestAlgoAvailable() { - if ( function_exists('crypt') && defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH == 1 ) + if ( function_exists('password_hash') ) + { + return OR_PASSWORD_ALGO_PHP_PASSWORD_HASH; + } + elseif ( function_exists('crypt') && defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH == 1 ) { return OR_PASSWORD_ALGO_CRYPT; } + elseif ( function_exists('sha1') ) + { + return OR_PASSWORD_ALGO_SHA1; + } elseif ( function_exists('md5') ) { return OR_PASSWORD_ALGO_MD5; @@ -34,26 +41,27 @@ class Password return OR_PASSWORD_ALGO_PLAIN; } } + + - /** * Hashen eines Kennwortes mit Bcrypt (bzw. MD5). * @param $password - * @param $algo Algo + * @param $algo int Algo * @param $cost Kostenfaktor: Eine Ganzzahl von 4 bis 31. */ static public function hash( $password,$algo,$cost=10 ) { switch( $algo ) { + case OR_PASSWORD_ALGO_PHP_PASSWORD_HASH: + + return password_hash( $password, PASSWORD_BCRYPT,array('cost'=>$cost) ); + case OR_PASSWORD_ALGO_CRYPT: + + $salt = Password::randomHexString(10); // this should be cryptographically safe. - $chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - $salt = ''; - for( $i = 1; $i <= 22; $i++ ) - $salt .= $chars[ rand(0,63) ]; - - // if ( version_compare(PHP_VERSION, '5.3.7') >= 0 ) $algo = '2y'; else @@ -68,6 +76,9 @@ class Password case OR_PASSWORD_ALGO_MD5: return md5($password); // ooold. + case OR_PASSWORD_ALGO_SHA1: + return sha1($password); // + case OR_PASSWORD_ALGO_PLAIN: return $password; // you want it, you get it. } @@ -84,38 +95,72 @@ class Password { switch( $algo ) { - case OR_PASSWORD_ALGO_MD5: - return $hash == md5($password); + case OR_PASSWORD_ALGO_PHP_PASSWORD_HASH: + // This is 'timing attack safe' as the documentation says. + return password_verify($password,$hash); case OR_PASSWORD_ALGO_CRYPT: if ( function_exists('crypt') ) { - // Workaround: Die Spalte 'password' ist z.Zt. nur 50 Stellen lang, daher + // Workaround: Die Spalte 'password' war frueher nur 50 Stellen lang, daher // wird der mit crypt() erzeugte Hash auf die Länge des gespeicherten Hashes - // gekürzt. Falls die Spalte später länger ist, wirkt automatisch die volle - // Hash-Länge. - return $hash == substr(crypt($password,$hash),0,strlen($hash)); + // gekürzt. Mit aktuellen Versionen gespeicherte Hashes haben die volle Länge. + return Password::equals( $hash, substr(crypt($password,$hash),0,strlen($hash)) ); } else { - throw new Exception("Modular crypt format is not supported by this PHP version (no function 'crypt()')"); + throw new LogicException("Modular crypt format is not supported by this PHP version (no function 'crypt()')"); } + + case OR_PASSWORD_ALGO_SHA1: + return Password::equals( $hash, sha1($password) ); + + case OR_PASSWORD_ALGO_MD5: + return Password::equals( $hash, md5($password) ); case OR_PASSWORD_ALGO_PLAIN: - return $hash == $password; + return Password::equals( $hash, $password ); } } + + /** + * Creates cryptographic safe random bytes in HEX format. + * @param int $bytesCount + * @return string HEX + */ static public function randomHexString( $bytesCount ) { + return bin2hex( Password::randomBytes($bytesCount) ); + } + + + /** + * Creates a cryptographic safe number. + * @param int $bytesCount + * @return int + */ + static public function randomNumber( $bytesCount ) + { + return bindec( Password::randomBytes($bytesCount) ); + } + + + /** + * Creates cryptographic safe random bytes. + * @param int $bytesCount + * @return string Binary bytes + */ + static public function randomBytes( $bytesCount ) + { if ( function_exists('random_bytes') ) { - return bin2hex( random_bytes($bytesCount) ); + return random_bytes($bytesCount); } elseif ( function_exists('openssl_random_pseudo_bytes') ) { - return bin2hex( openssl_random_pseudo_bytes($bytesCount) ); + return openssl_random_pseudo_bytes($bytesCount); } else { @@ -125,8 +170,48 @@ class Password { $buf .= chr(mt_rand(0, 255)); } - return bin2hex($buf); + return $buf; } } + + + /** + * Time-safe Compare of 2 strings. + * + * @param String $known + * @param String $user + * @return boolean true, if equal + */ + static private function equals( $known, $user ) + { + if( function_exists('hash_equals')) + { + return hash_equals($known, $user); + } + else + { + if ( strlen($known) != strlen($user) ) + { + return false; + } + else + { + $res = $known ^ $user; + $ret = 0; + for($i = strlen($res) - 1; $i >= 0; $i--) $ret |= ord($res[$i]); + return !$ret; + } + } + } + + + /** + * Cryptographic delay of execution. + * Delay is from 0 to 983 milliseconds, Steps of 15 microseconds, which would be very heavy to attack over a network. + */ + static public function delay() + { + time_nanosleep(0, Password::randomNumber(2)*15*1000); // delay: 0-983 ms + } } ?> \ No newline at end of file