openrat-cms

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

commit 578fb50dd7f32c478d921ab237f4fe7d401c0602
parent e7446c9274a073cb5c0239c624a7c331dea0bfd6
Author: Jan Dankert <devnull@localhost>
Date:   Sun,  3 Dec 2017 03:33:57 +0100

Refactoring: Security-Funktionen in ein eigenes "Modul" ausgelagert.

Diffstat:
action/UserAction.class.php | 2+-
auth/InternalAuth.class.php | 2+-
init.php | 2++
modules/security/Base2n.class.php | 304+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/security/Password.class.php | 254+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/security/require.php | 7+++++++
modules/util/require.php | 6++++++
util/Base2n.class.php | 304-------------------------------------------------------------------------------
util/Password.class.php | 218-------------------------------------------------------------------------------
util/include.inc.php | 2--
10 files changed, 575 insertions(+), 526 deletions(-)

diff --git a/action/UserAction.class.php b/action/UserAction.class.php @@ -226,7 +226,7 @@ class UserAction extends Action array('totpSecretUrl' => "otpauth://totp/{$issuer}:{$account}?secret={$secret}&issuer={$issuer}", 'hotpSecretUrl' => "otpauth://hotp/{$issuer}:{$account}?secret={$secret}&issuer={$issuer}&counter={$counter}" ) - + array('totpToken'=>$this->user->getTOTPCode()) + + array('totpToken'=>Password::getTOTPCode($this->user->otpSecret)) ); $this->setTemplateVar( 'allstyles',$this->user->getAvailableStyles() ); diff --git a/auth/InternalAuth.class.php b/auth/InternalAuth.class.php @@ -70,7 +70,7 @@ SQL { $user = new User($row_user['id']); $user->load(); - if ( $user->getTOTPCode() == $token ) + if ( Password::getTOTPCode($user->otpSecret) == $token ) return true; else return OR_AUTH_STATUS_TOKEN_NEEDED; diff --git a/init.php b/init.php @@ -117,7 +117,9 @@ require_once( OR_SERVICECLASSES_DIR."include.inc.".PHP_EXT ); require_once( OR_AUTHCLASSES_DIR."include.inc.".PHP_EXT ); +require_once( OR_MODULES_DIR."security/require.".PHP_EXT ); require_once( OR_MODULES_DIR."template-engine/require.".PHP_EXT ); +require_once( OR_MODULES_DIR."util/require.".PHP_EXT ); ?> \ No newline at end of file diff --git a/modules/security/Base2n.class.php b/modules/security/Base2n.class.php @@ -0,0 +1,303 @@ +<?php +/** + * Binary-to-text PHP Utilities + * + * @package binary-to-text-php + * @link https://github.com/ademarre/binary-to-text-php + * @author Andre DeMarre + * @copyright 2009-2013 Andre DeMarre + * @license http://opensource.org/licenses/MIT MIT + */ + +/** + * Class for binary-to-text encoding with a base of 2^n + * + * The Base2n class is for binary-to-text conversion. It employs a + * generalization of the algorithms used by many encoding schemes that + * use a fixed number of bits to encode each character. In other words, + * the base is a power of 2. + * + * Earlier versions of this class were named + * FixedBitNotation and FixedBitEncoding. + * + * @package binary-to-text-php + */ +class Base2n +{ + protected $_chars; + protected $_bitsPerCharacter; + protected $_radix; + protected $_rightPadFinalBits; + protected $_padFinalGroup; + protected $_padCharacter; + protected $_caseSensitive; + protected $_charmap; + + /** + * Constructor + * + * @param integer $bitsPerCharacter Bits to use for each encoded character + * @param string $chars Base character alphabet + * @param boolean $caseSensitive To decode in a case-sensitive manner + * @param boolean $rightPadFinalBits How to encode last character + * @param boolean $padFinalGroup Add padding to end of encoded output + * @param string $padCharacter Character to use for padding + * + * @throws InvalidArgumentException for incompatible parameters + */ + public function __construct( + $bitsPerCharacter, + $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_', + $caseSensitive = TRUE, $rightPadFinalBits = FALSE, + $padFinalGroup = FALSE, $padCharacter = '=') + { + // Ensure validity of $chars + if (!is_string($chars) || ($charLength = strlen($chars)) < 2) { + throw new InvalidArgumentException('$chars must be a string of at least two characters'); + } + + // Ensure validity of $padCharacter + if ($padFinalGroup) { + if (!is_string($padCharacter) || !isset($padCharacter[0])) { + throw new InvalidArgumentException('$padCharacter must be a string of one character'); + } + + if ($caseSensitive) { + $padCharFound = strpos($chars, $padCharacter[0]); + } else { + $padCharFound = stripos($chars, $padCharacter[0]); + } + + if ($padCharFound !== FALSE) { + throw new InvalidArgumentException('$padCharacter can not be a member of $chars'); + } + } + + // Ensure validity of $bitsPerCharacter + if (!is_int($bitsPerCharacter)) { + throw new InvalidArgumentException('$bitsPerCharacter must be an integer'); + } + + if ($bitsPerCharacter < 1) { + // $bitsPerCharacter must be at least 1 + throw new InvalidArgumentException('$bitsPerCharacter can not be less than 1'); + + } elseif ($charLength < 1 << $bitsPerCharacter) { + // Character length of $chars is too small for $bitsPerCharacter + // Find greatest acceptable value of $bitsPerCharacter + $bitsPerCharacter = 1; + $radix = 2; + + while ($charLength >= ($radix <<= 1) && $bitsPerCharacter < 8) { + $bitsPerCharacter++; + } + + $radix >>= 1; + throw new InvalidArgumentException( + '$bitsPerCharacter can not be more than ' . $bitsPerCharacter + . ' given $chars length of ' . $charLength + . ' (max radix ' . $radix . ')'); + + } elseif ($bitsPerCharacter > 8) { + // $bitsPerCharacter must not be greater than 8 + throw new InvalidArgumentException('$bitsPerCharacter can not be greater than 8'); + + } else { + $radix = 1 << $bitsPerCharacter; + } + + $this->_chars = $chars; + $this->_bitsPerCharacter = $bitsPerCharacter; + $this->_radix = $radix; + $this->_rightPadFinalBits = $rightPadFinalBits; + $this->_padFinalGroup = $padFinalGroup; + $this->_padCharacter = $padCharacter[0]; + $this->_caseSensitive = $caseSensitive; + } + + /** + * Encode a string + * + * @param string $rawString Binary data to encode + * @return string + */ + public function encode($rawString) + { + // Unpack string into an array of bytes + $bytes = unpack('C*', $rawString); + $byteCount = count($bytes); + + $encodedString = ''; + $byte = array_shift($bytes); + $bitsRead = 0; + $oldBits = 0; + + $chars = $this->_chars; + $bitsPerCharacter = $this->_bitsPerCharacter; + $rightPadFinalBits = $this->_rightPadFinalBits; + $padFinalGroup = $this->_padFinalGroup; + $padCharacter = $this->_padCharacter; + + $charsPerByte = 8 / $bitsPerCharacter; + $encodedLength = $byteCount * $charsPerByte; + + // Generate encoded output; each loop produces one encoded character + for ($c = 0; $c < $encodedLength; $c++) { + + // Get the bits needed for this encoded character + if ($bitsRead + $bitsPerCharacter > 8) { + // Not enough bits remain in this byte for the current character + // Save the remaining bits before getting the next byte + $oldBitCount = 8 - $bitsRead; + $oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount); + $newBitCount = $bitsPerCharacter - $oldBitCount; + + if (!$bytes) { + // Last bits; match final character and exit loop + if ($rightPadFinalBits) $oldBits <<= $newBitCount; + $encodedString .= $chars[$oldBits]; + + if ($padFinalGroup) { + // Array of the lowest common multiples of $bitsPerCharacter and 8, divided by 8 + $lcmMap = array(1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1); + $bytesPerGroup = $lcmMap[$bitsPerCharacter]; + $pads = $bytesPerGroup * $charsPerByte - ceil((strlen($rawString) % $bytesPerGroup) * $charsPerByte); + $encodedString .= str_repeat($padCharacter, $pads); + } + + break; + } + + // Get next byte + $byte = array_shift($bytes); + $bitsRead = 0; + + } else { + $oldBitCount = 0; + $newBitCount = $bitsPerCharacter; + } + + // Read only the needed bits from this byte + $bits = $byte >> 8 - ($bitsRead + ($newBitCount)); + $bits ^= $bits >> $newBitCount << $newBitCount; + $bitsRead += $newBitCount; + + if ($oldBitCount) { + // Bits come from seperate bytes, add $oldBits to $bits + $bits = ($oldBits << $newBitCount) | $bits; + } + + $encodedString .= $chars[$bits]; + } + + return $encodedString; + } + + /** + * Decode a string + * + * @param string $encodedString Data to decode + * @param boolean $strict Returns NULL if $encodedString contains an undecodable character + * @return string + */ + public function decode($encodedString, $strict = FALSE) + { + if (!$encodedString || !is_string($encodedString)) { + // Empty string, nothing to decode + return ''; + } + + $chars = $this->_chars; + $bitsPerCharacter = $this->_bitsPerCharacter; + $radix = $this->_radix; + $rightPadFinalBits = $this->_rightPadFinalBits; + $padFinalGroup = $this->_padFinalGroup; + $padCharacter = $this->_padCharacter; + $caseSensitive = $this->_caseSensitive; + + // Get index of encoded characters + if ($this->_charmap) { + $charmap = $this->_charmap; + + } else { + $charmap = array(); + + for ($i = 0; $i < $radix; $i++) { + $charmap[$chars[$i]] = $i; + } + + $this->_charmap = $charmap; + } + + // The last encoded character is $encodedString[$lastNotatedIndex] + $lastNotatedIndex = strlen($encodedString) - 1; + + // Remove trailing padding characters + if ($padFinalGroup) { + while ($encodedString[$lastNotatedIndex] === $padCharacter) { + $encodedString = substr($encodedString, 0, $lastNotatedIndex); + $lastNotatedIndex--; + } + } + + $rawString = ''; + $byte = 0; + $bitsWritten = 0; + + // Convert each encoded character to a series of unencoded bits + for ($c = 0; $c <= $lastNotatedIndex; $c++) { + + if (!$caseSensitive && !isset($charmap[$encodedString[$c]])) { + // Encoded character was not found; try other case + if (isset($charmap[$cUpper = strtoupper($encodedString[$c])])) { + $charmap[$encodedString[$c]] = $charmap[$cUpper]; + + } elseif (isset($charmap[$cLower = strtolower($encodedString[$c])])) { + $charmap[$encodedString[$c]] = $charmap[$cLower]; + } + } + + if (isset($charmap[$encodedString[$c]])) { + $bitsNeeded = 8 - $bitsWritten; + $unusedBitCount = $bitsPerCharacter - $bitsNeeded; + + // Get the new bits ready + if ($bitsNeeded > $bitsPerCharacter) { + // New bits aren't enough to complete a byte; shift them left into position + $newBits = $charmap[$encodedString[$c]] << $bitsNeeded - $bitsPerCharacter; + $bitsWritten += $bitsPerCharacter; + + } elseif ($c !== $lastNotatedIndex || $rightPadFinalBits) { + // Zero or more too many bits to complete a byte; shift right + $newBits = $charmap[$encodedString[$c]] >> $unusedBitCount; + $bitsWritten = 8; //$bitsWritten += $bitsNeeded; + + } else { + // Final bits don't need to be shifted + $newBits = $charmap[$encodedString[$c]]; + $bitsWritten = 8; + } + + $byte |= $newBits; + + if ($bitsWritten === 8 || $c === $lastNotatedIndex) { + // Byte is ready to be written + $rawString .= pack('C', $byte); + + if ($c !== $lastNotatedIndex) { + // Start the next byte + $bitsWritten = $unusedBitCount; + $byte = ($charmap[$encodedString[$c]] ^ ($newBits << $unusedBitCount)) << 8 - $bitsWritten; + } + } + + } elseif ($strict) { + // Unable to decode character; abort + return NULL; + } + } + + return $rawString; + } +} +?>+ \ No newline at end of file diff --git a/modules/security/Password.class.php b/modules/security/Password.class.php @@ -0,0 +1,253 @@ +<?php + +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); + + +/** + * Sicherheitsfunktionen für Passwörter. + * + * @author dankert + * + */ +class Password +{ + /** + * Ermittelt den bestverfügbarsten hash-Algorhytmus. + */ + static public function bestAlgoAvailable() + { + 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; + } + else + { + return OR_PASSWORD_ALGO_PLAIN; + } + } + + + + /** + * Hashen eines Kennwortes mit Bcrypt (bzw. MD5). + * @param $password + * @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. + + if ( version_compare(PHP_VERSION, '5.3.7') >= 0 ) + $algo = '2y'; + else + $algo = '2a'; + + // Kostenfaktor muss zwischen '04' und '31' (jeweils einschließlich) liegen. + $cost = max(min($cost,31),4); + $cost = str_pad($cost, 2, '0', STR_PAD_LEFT); + + return crypt($password,'$'.$algo.'$'.$cost.'$'.$salt.'$'); + + 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. + } + } + + /** + * Prüft das Kennwort gegen einen gespeicherten Hashwert. + * + * @param String $password Passwort + * @param String $hash Hash + * @return boolean true, falls das Passwort dem Hashwert entspricht. + */ + static public function check( $password,$hash,$algo ) + { + switch( $algo ) + { + 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' war frueher nur 50 Stellen lang, daher + // wird der mit crypt() erzeugte Hash auf die Länge des gespeicherten Hashes + // 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 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 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 random_bytes($bytesCount); + } + elseif ( function_exists('openssl_random_pseudo_bytes') ) + { + return openssl_random_pseudo_bytes($bytesCount); + } + else + { + // This fallback is NOT cryptographic safe! + $buf = ''; + for ($i = 0; $i < $length; ++$i) + { + $buf .= chr(mt_rand(0, 255)); + } + 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 168 milliseconds, Steps of 10 nanoseconds(!), which would be very heavy to attack over a network. + */ + static public function delay() + { + time_nanosleep(0, Password::randomNumber(3)*10); // delay: 0-167772150ns (= 0-~168ms) + } + + + + + + /** + * Calculate the code, with given secret and point in time. + * + * @param string $secret + * @param int|null $timeSlice + * + * @return string + */ + public static function getTOTPCode( $secret ) + { + $codeLength = 6; + $timeSlice = floor(time() / 30); + $secretkey = @hex2bin($secret); + // Pack time into binary string + $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); + // Hash it with users secret key + $hm = hash_hmac('SHA1', $time, $secretkey, true); + // Use last nipple of result as index/offset + $offset = ord(substr($hm, -1)) & 0x0F; + // grab 4 bytes of the result + $hashpart = substr($hm, $offset, 4); + // Unpak binary value + $value = unpack('N', $hashpart); + $value = $value[1]; + // Only 32 bits + $value = $value & 0x7FFFFFFF; + $modulo = pow(10, $codeLength); + return str_pad($value % $modulo, $codeLength, '0', STR_PAD_LEFT); + } + + +} +?>+ \ No newline at end of file diff --git a/modules/security/require.php b/modules/security/require.php @@ -0,0 +1,6 @@ +<?php + +include( dirname(__FILE__) . '/Base2n.class.php'); +include( dirname(__FILE__) . '/Password.class.php'); + +?>+ \ No newline at end of file diff --git a/modules/util/require.php b/modules/util/require.php @@ -0,0 +1,5 @@ +<?php + +//include( dirname(__FILE__) . '/.class.php'); + +?>+ \ No newline at end of file diff --git a/util/Base2n.class.php b/util/Base2n.class.php @@ -1,303 +0,0 @@ -<?php -/** - * Binary-to-text PHP Utilities - * - * @package binary-to-text-php - * @link https://github.com/ademarre/binary-to-text-php - * @author Andre DeMarre - * @copyright 2009-2013 Andre DeMarre - * @license http://opensource.org/licenses/MIT MIT - */ - -/** - * Class for binary-to-text encoding with a base of 2^n - * - * The Base2n class is for binary-to-text conversion. It employs a - * generalization of the algorithms used by many encoding schemes that - * use a fixed number of bits to encode each character. In other words, - * the base is a power of 2. - * - * Earlier versions of this class were named - * FixedBitNotation and FixedBitEncoding. - * - * @package binary-to-text-php - */ -class Base2n -{ - protected $_chars; - protected $_bitsPerCharacter; - protected $_radix; - protected $_rightPadFinalBits; - protected $_padFinalGroup; - protected $_padCharacter; - protected $_caseSensitive; - protected $_charmap; - - /** - * Constructor - * - * @param integer $bitsPerCharacter Bits to use for each encoded character - * @param string $chars Base character alphabet - * @param boolean $caseSensitive To decode in a case-sensitive manner - * @param boolean $rightPadFinalBits How to encode last character - * @param boolean $padFinalGroup Add padding to end of encoded output - * @param string $padCharacter Character to use for padding - * - * @throws InvalidArgumentException for incompatible parameters - */ - public function __construct( - $bitsPerCharacter, - $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_', - $caseSensitive = TRUE, $rightPadFinalBits = FALSE, - $padFinalGroup = FALSE, $padCharacter = '=') - { - // Ensure validity of $chars - if (!is_string($chars) || ($charLength = strlen($chars)) < 2) { - throw new InvalidArgumentException('$chars must be a string of at least two characters'); - } - - // Ensure validity of $padCharacter - if ($padFinalGroup) { - if (!is_string($padCharacter) || !isset($padCharacter[0])) { - throw new InvalidArgumentException('$padCharacter must be a string of one character'); - } - - if ($caseSensitive) { - $padCharFound = strpos($chars, $padCharacter[0]); - } else { - $padCharFound = stripos($chars, $padCharacter[0]); - } - - if ($padCharFound !== FALSE) { - throw new InvalidArgumentException('$padCharacter can not be a member of $chars'); - } - } - - // Ensure validity of $bitsPerCharacter - if (!is_int($bitsPerCharacter)) { - throw new InvalidArgumentException('$bitsPerCharacter must be an integer'); - } - - if ($bitsPerCharacter < 1) { - // $bitsPerCharacter must be at least 1 - throw new InvalidArgumentException('$bitsPerCharacter can not be less than 1'); - - } elseif ($charLength < 1 << $bitsPerCharacter) { - // Character length of $chars is too small for $bitsPerCharacter - // Find greatest acceptable value of $bitsPerCharacter - $bitsPerCharacter = 1; - $radix = 2; - - while ($charLength >= ($radix <<= 1) && $bitsPerCharacter < 8) { - $bitsPerCharacter++; - } - - $radix >>= 1; - throw new InvalidArgumentException( - '$bitsPerCharacter can not be more than ' . $bitsPerCharacter - . ' given $chars length of ' . $charLength - . ' (max radix ' . $radix . ')'); - - } elseif ($bitsPerCharacter > 8) { - // $bitsPerCharacter must not be greater than 8 - throw new InvalidArgumentException('$bitsPerCharacter can not be greater than 8'); - - } else { - $radix = 1 << $bitsPerCharacter; - } - - $this->_chars = $chars; - $this->_bitsPerCharacter = $bitsPerCharacter; - $this->_radix = $radix; - $this->_rightPadFinalBits = $rightPadFinalBits; - $this->_padFinalGroup = $padFinalGroup; - $this->_padCharacter = $padCharacter[0]; - $this->_caseSensitive = $caseSensitive; - } - - /** - * Encode a string - * - * @param string $rawString Binary data to encode - * @return string - */ - public function encode($rawString) - { - // Unpack string into an array of bytes - $bytes = unpack('C*', $rawString); - $byteCount = count($bytes); - - $encodedString = ''; - $byte = array_shift($bytes); - $bitsRead = 0; - $oldBits = 0; - - $chars = $this->_chars; - $bitsPerCharacter = $this->_bitsPerCharacter; - $rightPadFinalBits = $this->_rightPadFinalBits; - $padFinalGroup = $this->_padFinalGroup; - $padCharacter = $this->_padCharacter; - - $charsPerByte = 8 / $bitsPerCharacter; - $encodedLength = $byteCount * $charsPerByte; - - // Generate encoded output; each loop produces one encoded character - for ($c = 0; $c < $encodedLength; $c++) { - - // Get the bits needed for this encoded character - if ($bitsRead + $bitsPerCharacter > 8) { - // Not enough bits remain in this byte for the current character - // Save the remaining bits before getting the next byte - $oldBitCount = 8 - $bitsRead; - $oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount); - $newBitCount = $bitsPerCharacter - $oldBitCount; - - if (!$bytes) { - // Last bits; match final character and exit loop - if ($rightPadFinalBits) $oldBits <<= $newBitCount; - $encodedString .= $chars[$oldBits]; - - if ($padFinalGroup) { - // Array of the lowest common multiples of $bitsPerCharacter and 8, divided by 8 - $lcmMap = array(1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1); - $bytesPerGroup = $lcmMap[$bitsPerCharacter]; - $pads = $bytesPerGroup * $charsPerByte - ceil((strlen($rawString) % $bytesPerGroup) * $charsPerByte); - $encodedString .= str_repeat($padCharacter, $pads); - } - - break; - } - - // Get next byte - $byte = array_shift($bytes); - $bitsRead = 0; - - } else { - $oldBitCount = 0; - $newBitCount = $bitsPerCharacter; - } - - // Read only the needed bits from this byte - $bits = $byte >> 8 - ($bitsRead + ($newBitCount)); - $bits ^= $bits >> $newBitCount << $newBitCount; - $bitsRead += $newBitCount; - - if ($oldBitCount) { - // Bits come from seperate bytes, add $oldBits to $bits - $bits = ($oldBits << $newBitCount) | $bits; - } - - $encodedString .= $chars[$bits]; - } - - return $encodedString; - } - - /** - * Decode a string - * - * @param string $encodedString Data to decode - * @param boolean $strict Returns NULL if $encodedString contains an undecodable character - * @return string - */ - public function decode($encodedString, $strict = FALSE) - { - if (!$encodedString || !is_string($encodedString)) { - // Empty string, nothing to decode - return ''; - } - - $chars = $this->_chars; - $bitsPerCharacter = $this->_bitsPerCharacter; - $radix = $this->_radix; - $rightPadFinalBits = $this->_rightPadFinalBits; - $padFinalGroup = $this->_padFinalGroup; - $padCharacter = $this->_padCharacter; - $caseSensitive = $this->_caseSensitive; - - // Get index of encoded characters - if ($this->_charmap) { - $charmap = $this->_charmap; - - } else { - $charmap = array(); - - for ($i = 0; $i < $radix; $i++) { - $charmap[$chars[$i]] = $i; - } - - $this->_charmap = $charmap; - } - - // The last encoded character is $encodedString[$lastNotatedIndex] - $lastNotatedIndex = strlen($encodedString) - 1; - - // Remove trailing padding characters - if ($padFinalGroup) { - while ($encodedString[$lastNotatedIndex] === $padCharacter) { - $encodedString = substr($encodedString, 0, $lastNotatedIndex); - $lastNotatedIndex--; - } - } - - $rawString = ''; - $byte = 0; - $bitsWritten = 0; - - // Convert each encoded character to a series of unencoded bits - for ($c = 0; $c <= $lastNotatedIndex; $c++) { - - if (!$caseSensitive && !isset($charmap[$encodedString[$c]])) { - // Encoded character was not found; try other case - if (isset($charmap[$cUpper = strtoupper($encodedString[$c])])) { - $charmap[$encodedString[$c]] = $charmap[$cUpper]; - - } elseif (isset($charmap[$cLower = strtolower($encodedString[$c])])) { - $charmap[$encodedString[$c]] = $charmap[$cLower]; - } - } - - if (isset($charmap[$encodedString[$c]])) { - $bitsNeeded = 8 - $bitsWritten; - $unusedBitCount = $bitsPerCharacter - $bitsNeeded; - - // Get the new bits ready - if ($bitsNeeded > $bitsPerCharacter) { - // New bits aren't enough to complete a byte; shift them left into position - $newBits = $charmap[$encodedString[$c]] << $bitsNeeded - $bitsPerCharacter; - $bitsWritten += $bitsPerCharacter; - - } elseif ($c !== $lastNotatedIndex || $rightPadFinalBits) { - // Zero or more too many bits to complete a byte; shift right - $newBits = $charmap[$encodedString[$c]] >> $unusedBitCount; - $bitsWritten = 8; //$bitsWritten += $bitsNeeded; - - } else { - // Final bits don't need to be shifted - $newBits = $charmap[$encodedString[$c]]; - $bitsWritten = 8; - } - - $byte |= $newBits; - - if ($bitsWritten === 8 || $c === $lastNotatedIndex) { - // Byte is ready to be written - $rawString .= pack('C', $byte); - - if ($c !== $lastNotatedIndex) { - // Start the next byte - $bitsWritten = $unusedBitCount; - $byte = ($charmap[$encodedString[$c]] ^ ($newBits << $unusedBitCount)) << 8 - $bitsWritten; - } - } - - } elseif ($strict) { - // Unable to decode character; abort - return NULL; - } - } - - return $rawString; - } -} -?>- \ No newline at end of file diff --git a/util/Password.class.php b/util/Password.class.php @@ -1,217 +0,0 @@ -<?php - -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); - - -/** - * Sicherheitsfunktionen für Passwörter. - * - * @author dankert - * - */ -class Password -{ - /** - * Ermittelt den bestverfügbarsten hash-Algorhytmus. - */ - static public function bestAlgoAvailable() - { - 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; - } - else - { - return OR_PASSWORD_ALGO_PLAIN; - } - } - - - - /** - * Hashen eines Kennwortes mit Bcrypt (bzw. MD5). - * @param $password - * @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. - - if ( version_compare(PHP_VERSION, '5.3.7') >= 0 ) - $algo = '2y'; - else - $algo = '2a'; - - // Kostenfaktor muss zwischen '04' und '31' (jeweils einschließlich) liegen. - $cost = max(min($cost,31),4); - $cost = str_pad($cost, 2, '0', STR_PAD_LEFT); - - return crypt($password,'$'.$algo.'$'.$cost.'$'.$salt.'$'); - - 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. - } - } - - /** - * Prüft das Kennwort gegen einen gespeicherten Hashwert. - * - * @param String $password Passwort - * @param String $hash Hash - * @return boolean true, falls das Passwort dem Hashwert entspricht. - */ - static public function check( $password,$hash,$algo ) - { - switch( $algo ) - { - 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' war frueher nur 50 Stellen lang, daher - // wird der mit crypt() erzeugte Hash auf die Länge des gespeicherten Hashes - // 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 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 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 random_bytes($bytesCount); - } - elseif ( function_exists('openssl_random_pseudo_bytes') ) - { - return openssl_random_pseudo_bytes($bytesCount); - } - else - { - // This fallback is NOT cryptographic safe! - $buf = ''; - for ($i = 0; $i < $length; ++$i) - { - $buf .= chr(mt_rand(0, 255)); - } - 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 168 milliseconds, Steps of 10 nanoseconds(!), which would be very heavy to attack over a network. - */ - static public function delay() - { - time_nanosleep(0, Password::randomNumber(3)*10); // delay: 0-167772150ns (= 0-~168ms) - } -} -?>- \ No newline at end of file diff --git a/util/include.inc.php b/util/include.inc.php @@ -1,7 +1,6 @@ <?php require_once( OR_SERVICECLASSES_DIR."GlobalFunctions.class.".PHP_EXT ); -require_once( OR_SERVICECLASSES_DIR."Base2n.class.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."Http.class.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."Html.class.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."Text.class.".PHP_EXT ); @@ -12,7 +11,6 @@ require_once( OR_SERVICECLASSES_DIR."FileUtils.class.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."JSON.class.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."Less.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."JSqueeze.class.".PHP_EXT ); -require_once( OR_SERVICECLASSES_DIR."Password.class.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."Spyc.class.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."exception/OpenRatException.class.".PHP_EXT ); require_once( OR_SERVICECLASSES_DIR."exception/SecurityException.class.".PHP_EXT );