From 200f4d5c141344f09c8a6febae1571f9fdc1a19d Mon Sep 17 00:00:00 2001 From: kambereBr Date: Tue, 9 Jul 2024 16:36:07 +0200 Subject: [PATCH] Use non-multibyte functions for non-mb-string hashes --- lib/crypt.php | 18 +++++++------- lib/crypt_sodium.php | 8 +++--- lib/scram.php | 10 ++++---- modules/2fa/modules.php | 6 ++--- modules/imap/hm-imap.php | 4 +-- modules/imap/hm-jmap.php | 2 +- modules/smtp/hm-smtp.php | 48 ++++++++++++++++++------------------ tests/phpunit/crypt_base.php | 8 +++--- 8 files changed, 52 insertions(+), 52 deletions(-) diff --git a/lib/crypt.php b/lib/crypt.php index d05c536432..dbb35d777f 100644 --- a/lib/crypt.php +++ b/lib/crypt.php @@ -76,18 +76,18 @@ public static function plaintext($string, $key) { $string = base64_decode($string); /* bail if the crypt text is invalid */ - if (!$string || mb_strlen($string, '8bit') <= 200) { + if (!$string || strlen($string) <= 200) { return false; } /* get the payload and salt */ - $crypt_string = mb_substr($string, 192, null, '8bit'); - $salt = mb_substr($string, 0, 128, '8bit'); + $crypt_string = substr($string, 192); + $salt = substr($string, 0, 128); /* check the signature. Temporarily allow the same key for hmac validation, eventually remove the $encryption_rounds * check and require the hmac_rounds check only! */ - if (!self::check_hmac($crypt_string, mb_substr($string, 128, 64, '8bit'), $salt, $key, self::$hmac_rounds) && - !self::check_hmac($crypt_string, mb_substr($string, 128, 64, '8bit'), $salt, $key, self::$encryption_rounds)) { + if (!self::check_hmac($crypt_string, substr($string, 128, 64), $salt, $key, self::$hmac_rounds) && + !self::check_hmac($crypt_string, substr($string, 128, 64), $salt, $key, self::$encryption_rounds)) { Hm_Debug::add('HMAC verification failed'); return false; } @@ -160,7 +160,7 @@ public static function generate_salt() { */ private static function hash_equals($a, $b) { $res = 0; - $len = mb_strlen($a, '8bit'); + $len = strlen($a); for ($i = 0; $i < $len; $i++) { $res |= ord($a[$i]) ^ ord($b[$i]); } @@ -176,7 +176,7 @@ private static function hash_equals($a, $b) { * @return bool */ public static function hash_compare($a, $b) { - if (!is_string($a) || !is_string($b) || mb_strlen($a, '8bit') !== mb_strlen($b, '8bit')) { + if (!is_string($a) || !is_string($b) || strlen($a) !== strlen($b)) { return false; } /* requires PHP >= 7.4 */ @@ -212,7 +212,7 @@ public static function pbkdf2($key, $salt, $length, $count, $algo) { } /* manual version */ - $size = mb_strlen(hash($algo, '', true), '8bit'); + $size = strlen(hash($algo, '', true)); $len = ceil($length / $size); $result = ''; for ($i = 1; $i <= $len; $i++) { @@ -224,7 +224,7 @@ public static function pbkdf2($key, $salt, $length, $count, $algo) { } $result .= $res; } - return mb_substr($result, 0, $length, '8bit'); + return substr($result, 0, $length); } /** diff --git a/lib/crypt_sodium.php b/lib/crypt_sodium.php index 0ac6a32b85..bd79b44b29 100644 --- a/lib/crypt_sodium.php +++ b/lib/crypt_sodium.php @@ -26,12 +26,12 @@ public static function plaintext($string, $key) { } $res = false; $raw_string = base64_decode($string); - if (!$raw_string || mb_strlen($raw_string, '8bit') < 60) { + if (!$raw_string || strlen($raw_string) < 60) { return false; } - list($salt, $crypt_key) = self::keygen($key, mb_substr($raw_string, 0, 24, '8bit')); - $hmac = mb_substr($raw_string, 24, 32, '8bit'); - $crypt_string = mb_substr($raw_string, 56, null, '8bit'); + list($salt, $crypt_key) = self::keygen($key, substr($raw_string, 0, 24)); + $hmac = substr($raw_string, 24, 32); + $crypt_string = substr($raw_string, 56); if (Hm_Sodium_Compat::crypto_auth_verify($hmac, $crypt_string, $crypt_key)) { $res = Hm_Sodium_Compat::crypto_secretbox_open($crypt_string, $salt, $crypt_key); diff --git a/lib/scram.php b/lib/scram.php index 7b0e83e22f..3d75193c88 100644 --- a/lib/scram.php +++ b/lib/scram.php @@ -31,7 +31,7 @@ private function log($message) { } public function generateClientProof($username, $password, $salt, $clientNonce, $serverNonce, $algorithm) { $iterations = 4096; - $keyLength = mb_strlen(hash($algorithm, '', true), '8bit'); // Dynamically determine key length based on algorithm + $keyLength = strlen(hash($algorithm, '', true)); // Dynamically determine key length based on algorithm $passwordBytes = hash($algorithm, $password, true); $saltedPassword = hash_pbkdf2($algorithm, $passwordBytes, $salt, $iterations, $keyLength, true); @@ -56,8 +56,8 @@ public function authenticateScram($scramAlgorithm, $username, $password, $getSer // Extract salt and server nonce from the server's challenge $serverChallenge = base64_decode(mb_substr($response[0], 2)); $parts = explode(',', $serverChallenge); - $serverNonce = base64_decode(mb_substr($parts[0], mb_strpos($parts[0], "=", 0, '8bit') + 1, null, '8bit')); - $salt = base64_decode(mb_substr($parts[1], mb_strpos($parts[1], "=", 0, '8bit') + 1, null, '8bit')); + $serverNonce = base64_decode(substr($parts[0], strpos($parts[0], "=") + 1)); + $salt = base64_decode(substr($parts[1], strpos($parts[1], "=") + 1)); // Generate client nonce $clientNonce = base64_encode(random_bytes(32)); @@ -79,11 +79,11 @@ public function authenticateScram($scramAlgorithm, $username, $password, $getSer if (!empty($response) && mb_substr($response[0], 0, 2) == '+ ') { $serverFinalMessage = base64_decode(mb_substr($response[0], 2)); $parts = explode(',', $serverFinalMessage); - $serverProof = mb_substr($parts[0], mb_strpos($parts[0], "=", 0, '8bit') + 1, null, '8bit'); + $serverProof = substr($parts[0], strpos($parts[0], "=") + 1); // Generate server key $passwordBytes = hash($algorithm, $password, true); - $saltedPassword = hash_pbkdf2($algorithm, $passwordBytes, $salt, 4096, mb_strlen(hash($algorithm, '', true), '8bit'), true); + $saltedPassword = hash_pbkdf2($algorithm, $passwordBytes, $salt, 4096, strlen(hash($algorithm, '', true)), true); $serverKey = hash_hmac($algorithm, "Server Key", $saltedPassword, true); // Calculate server signature diff --git a/modules/2fa/modules.php b/modules/2fa/modules.php index 519a6c64ce..b12c410207 100644 --- a/modules/2fa/modules.php +++ b/modules/2fa/modules.php @@ -320,9 +320,9 @@ function check_2fa_pin($pin, $secret, $pass_len=6) { $time = pack('N', $time); $time = str_pad($time, 8, chr(0), STR_PAD_LEFT); $hash = hash_hmac('sha1', $time, $secret, true); - $offset = ord(mb_substr($hash,-1, null, '8bit')) & 0xF; - $input = mb_substr($hash, $offset, mb_strlen($hash, '8bit') - $offset, '8bit'); - $input = unpack("N",mb_substr($input, 0, 4, '8bit')); + $offset = ord(substr($hash,-1)) & 0xF; + $input = substr($hash, $offset, strlen($hash) - $offset); + $input = unpack("N",substr($input, 0, 4)); $inthash = $input[1] & 0x7FFFFFFF; return $pin === str_pad($inthash % $pin_mod, 6, "0", STR_PAD_LEFT); }} diff --git a/modules/imap/hm-imap.php b/modules/imap/hm-imap.php index f4c2f47e95..a41847793c 100644 --- a/modules/imap/hm-imap.php +++ b/modules/imap/hm-imap.php @@ -272,8 +272,8 @@ public function authenticate($username, $password) { $cram1 = 'AUTHENTICATE CRAM-MD5' . "\r\n"; $this->send_command($cram1); $response = $this->get_response(); - $challenge = base64_decode(mb_substr(trim($response[0]), 1, null, '8bit')); - $pass = str_repeat(chr(0x00), (64-mb_strlen($password, '8bit'))); + $challenge = base64_decode(substr(trim($response[0]), 1)); + $pass = str_repeat(chr(0x00), (64-strlen($password))); $ipad = str_repeat(chr(0x36), 64); $opad = str_repeat(chr(0x5c), 64); $digest = bin2hex(pack("H*", md5(($pass ^ $opad) . pack("H*", md5(($pass ^ $ipad) . $challenge))))); diff --git a/modules/imap/hm-jmap.php b/modules/imap/hm-jmap.php index a2257270f4..46b28040e9 100644 --- a/modules/imap/hm-jmap.php +++ b/modules/imap/hm-jmap.php @@ -199,7 +199,7 @@ public function start_message_stream($uid, $message_part) { return 0; } $this->streaming_msg = $this->get_raw_message_content($blob_id, $name); - return mb_strlen($this->streaming_msg, '8bit'); + return strlen($this->streaming_msg); } /** diff --git a/modules/smtp/hm-smtp.php b/modules/smtp/hm-smtp.php index ce9bd46d3f..9aab4b00ea 100644 --- a/modules/smtp/hm-smtp.php +++ b/modules/smtp/hm-smtp.php @@ -362,7 +362,7 @@ function authenticate($username, $password, $mech) { $result = 'FATAL: SMTP server does not support AUTH CRAM-MD5'; } else { $challenge = base64_decode(trim($response[0][1][0])); - $password .= str_repeat(chr(0x00), (64-mb_strlen($password, '8bit'))); + $password .= str_repeat(chr(0x00), (64-strlen($password))); $ipad = str_repeat(chr(0x36), 64); $opad = str_repeat(chr(0x5c), 64); $digest = bin2hex(pack('H*', md5(($password ^ $opad).pack('H*', md5(($password ^ $ipad).$challenge))))); @@ -429,8 +429,8 @@ function authenticate($username, $password, $mech) { function parse_ntlm_type_two($bin_str) { $res = array(); $res['vals'] = unpack('a8prefix/Vtype/vname_len/vname_space/Vname_offset/Vflags/A8challenge/A8context/vtarget_len/vtarget_space/Vtarget_offset', base64_decode($bin_str)); - $res['name'] = unpack('A'.$res['vals']['name_len'].'name', mb_substr(base64_decode($bin_str), $res['vals']['name_offset'], $res['vals']['name_len'], '8bit')); - $target = mb_substr(base64_decode($bin_str), $res['vals']['target_offset'], $res['vals']['target_len'], '8bit'); + $res['name'] = unpack('A'.$res['vals']['name_len'].'name', substr(base64_decode($bin_str), $res['vals']['name_offset'], $res['vals']['name_len'])); + $target = substr(base64_decode($bin_str), $res['vals']['target_offset'], $res['vals']['target_len']); $flds = array(2 => 'domain', 1 => 'server', 4 => 'dns_domain', 3 => 'dns_server'); $names = array('domain' => '', 'server' => '', 'dns_domain' => '', 'dns_server' => ''); while ($target) { @@ -438,11 +438,11 @@ function parse_ntlm_type_two($bin_str) { if ($atts['fld'] == 0) { break; } - $fld = unpack('A'.$atts['len'], mb_substr($target, 4, null, '8bit')); + $fld = unpack('A'.$atts['len'], substr($target, 4)); if (isset($flds[$atts['fld']])) { $names[$flds[$atts['fld']]] = $fld; } - $target = mb_substr($target, (4 + $atts['len']), null, '8bit'); + $target = substr($target, (4 + $atts['len'])); } $res['names'] = $names; return $res; @@ -466,17 +466,17 @@ function build_ntlm_type_three($msg_data, $username, $password) { $lm_response = $this->build_lm_response($msg_data, $username, $password); $ntlm_response = $this->build_ntlm_response($msg_data, $username, $password); $flags = pack('V', 0x00000201); - $offset = mb_strlen($pre.$type, '8bit')+52; - $target_sec = $this->ntlm_security_buffer(mb_strlen($target, '8bit'), $offset); - $offset += mb_strlen($target, '8bit'); + $offset = strlen($pre.$type)+52; + $target_sec = $this->ntlm_security_buffer(strlen($target), $offset); + $offset += strlen($target); $user_sec = $this->ntlm_security_buffer(mb_strlen($username), $offset); $offset += mb_strlen($username); - $host_sec = $this->ntlm_security_buffer(mb_strlen($host, '8bit'), $offset); + $host_sec = $this->ntlm_security_buffer(strlen($host), $offset); $offset += mb_strlen($host); - $lm_sec = $this->ntlm_security_buffer(mb_strlen($lm_response, '8bit'), $offset); - $offset += mb_strlen($lm_response, '8bit'); - $ntlm_sec = $this->ntlm_security_buffer(mb_strlen($ntlm_response, '8bit'), $offset); - $offset += mb_strlen($ntlm_response, '8bit'); + $lm_sec = $this->ntlm_security_buffer(strlen($lm_response), $offset); + $offset += strlen($lm_response); + $ntlm_sec = $this->ntlm_security_buffer(strlen($ntlm_response), $offset); + $offset += strlen($ntlm_response); $sess_sec = $this->ntlm_security_buffer(0, $offset); return base64_encode($pre.$type.$lm_sec.$ntlm_sec.$target_sec.$user_sec.$host_sec.$sess_sec.$flags.$target.$username.$host.$lm_response.$ntlm_response); } @@ -488,17 +488,17 @@ function ntlm_security_buffer($len, $offset) { /* build the NTLM lm hash then ecnrypt the challenge string with it */ function build_lm_response($msg_data, $username, $password){ - $pass = mb_strtoupper($password, '8bit'); - while (mb_strlen($pass, '8bit') < 14) { + $pass = strtoupper($password); + while (strlen($pass) < 14) { $pass .= chr(0); } - if (mb_strlen($pass, '8bit') > 14) { + if (strlen($pass) > 14) { return str_repeat(chr(0), 16); } - $p1 = mb_substr($pass, 0, 7, '8bit'); - $p2 = mb_substr($pass, 7, null, '8bit'); + $p1 = substr($pass, 0, 7); + $p2 = substr($pass, 7); $lm_hash = $this->des_encrypt($p1).$this->des_encrypt($p2); - while (mb_strlen($lm_hash, '8bit') < 21) { + while (strlen($lm_hash) < 21) { $lm_hash .= chr(0); } return $this->apply_ntlm_hash($msg_data['vals']['challenge'], $lm_hash); @@ -508,7 +508,7 @@ function build_lm_response($msg_data, $username, $password){ function build_ntlm_response($msg_data, $username, $password){ $password = iconv('UTF-8', 'UTF-16LE', $password); $ntlm_hash = hash('md4', $password, true); - while (mb_strlen($ntlm_hash, '8bit') < 21) { + while (strlen($ntlm_hash) < 21) { $ntlm_hash .= chr(0); } return $this->apply_ntlm_hash($msg_data['vals']['challenge'], $ntlm_hash); @@ -516,9 +516,9 @@ function build_ntlm_response($msg_data, $username, $password){ /* encrypt the challenge string with the lm/ntlm hash */ function apply_ntlm_hash($challenge, $hash) { - $p1 = mb_substr($hash, 0, 7, '8bit'); - $p2 = mb_substr($hash, 7, 7, '8bit'); - $p3 = mb_substr($hash, 14, 7, '8bit'); + $p1 = substr($hash, 0, 7); + $p2 = substr($hash, 7, 7); + $p3 = substr($hash, 14, 7); return $this->des_encrypt($p1, $challenge). $this->des_encrypt($p2, $challenge). $this->des_encrypt($p3, $challenge); @@ -528,7 +528,7 @@ function apply_ntlm_hash($challenge, $hash) { function des_encrypt($string, $challenge='KGS!@#$%') { $key = array(); $tmp = array(); - $len = mb_strlen($string, '8bit'); + $len = strlen($string); for ($i=0; $i<7; ++$i) $tmp[] = $i < $len ? ord($string[$i]) : 0; $key[] = $tmp[0] & 254; diff --git a/tests/phpunit/crypt_base.php b/tests/phpunit/crypt_base.php index f456f4662b..5860a0c59d 100644 --- a/tests/phpunit/crypt_base.php +++ b/tests/phpunit/crypt_base.php @@ -17,7 +17,7 @@ public function setUp(): void { */ public function test_random() { $bytes = Hm_Crypt::random(10); - $this->assertTrue(mb_strlen($bytes, '8bit') == 10); + $this->assertTrue(strlen($bytes) == 10); Hm_Functions::$rand_bytes = 'bad'; try { Hm_Crypt::random(10); @@ -104,8 +104,8 @@ public function test_pbkdf2() { * @runInSeparateProcess */ public function test_unique_id() { - $this->assertEquals(24, mb_strlen(base64_decode(Hm_Crypt::unique_id(24)), '8bit')); - $this->assertEquals(48, mb_strlen(base64_decode(Hm_Crypt::unique_id(48)), '8bit')); - $this->assertEquals(128, mb_strlen(base64_decode(Hm_Crypt::unique_id()), '8bit')); + $this->assertEquals(24, strlen(base64_decode(Hm_Crypt::unique_id(24)))); + $this->assertEquals(48, strlen(base64_decode(Hm_Crypt::unique_id(48)))); + $this->assertEquals(128, strlen(base64_decode(Hm_Crypt::unique_id()))); } }