2010-04-07 2 views
1

phpass является широко используемым хэшированием «рамки». Оценивая phpass 'HashPassword, я наткнулся на этот фрагмент нечетного метода.Как может быть безопасный хешированный пароль?

function HashPassword($password) 
    { 
     // <snip> trying to generate a hash… 

     # Returning '*' on error is safe here, but would _not_ be safe 
     # in a crypt(3)-like function used _both_ for generating new 
     # hashes and for validating passwords against existing hashes. 
     return '*'; 
    } 

Ответ: мы согласны с тем, этот класс предполагает, что мы тестируем нашу хэш равенства на * как средство проверки. Вот почему я завершу этот класс, потому что это интерфейс невелик. Я ожидаю ложь в случае неудачи.


Это полный класс phpsalt:

# Portable PHP password hashing framework. 
# 
# Version 0.2/genuine. 
# 
# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in 
# the public domain. 
# 
# 
# 
class PasswordHash { 
    var $itoa64; 
    var $iteration_count_log2; 
    var $portable_hashes; 
    var $random_state; 

    function PasswordHash($iteration_count_log2, $portable_hashes) 
    { 
     $this->itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 

     if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) 
      $iteration_count_log2 = 8; 
     $this->iteration_count_log2 = $iteration_count_log2; 

     $this->portable_hashes = $portable_hashes; 

     $this->random_state = microtime() . getmypid(); 
    } 

    function get_random_bytes($count) 
    { 
     $output = ''; 
     if (is_readable('/dev/urandom') && 
      ($fh = @fopen('/dev/urandom', 'rb'))) { 
      $output = fread($fh, $count); 
      fclose($fh); 
     } 

     if (strlen($output) < $count) { 
      $output = ''; 
      for ($i = 0; $i < $count; $i += 16) { 
       $this->random_state = 
        md5(microtime() . $this->random_state); 
       $output .= 
        pack('H*', md5($this->random_state)); 
      } 
      $output = substr($output, 0, $count); 
     } 

     return $output; 
    } 

    function encode64($input, $count) 
    { 
     $output = ''; 
     $i = 0; 
     do { 
      $value = ord($input[$i++]); 
      $output .= $this->itoa64[$value & 0x3f]; 
      if ($i < $count) 
       $value |= ord($input[$i]) << 8; 
      $output .= $this->itoa64[($value >> 6) & 0x3f]; 
      if ($i++ >= $count) 
       break; 
      if ($i < $count) 
       $value |= ord($input[$i]) << 16; 
      $output .= $this->itoa64[($value >> 12) & 0x3f]; 
      if ($i++ >= $count) 
       break; 
      $output .= $this->itoa64[($value >> 18) & 0x3f]; 
     } while ($i < $count); 

     return $output; 
    } 

    function gensalt_private($input) 
    { 
     $output = '$P$'; 
     $output .= $this->itoa64[min($this->iteration_count_log2 + 
      ((PHP_VERSION >= '5') ? 5 : 3), 30)]; 
     $output .= $this->encode64($input, 6); 

     return $output; 
    } 

    function crypt_private($password, $setting) 
    { 
     $output = '*0'; 
     if (substr($setting, 0, 2) == $output) 
      $output = '*1'; 

     if (substr($setting, 0, 3) != '$P$') 
      return $output; 

     $count_log2 = strpos($this->itoa64, $setting[3]); 
     if ($count_log2 < 7 || $count_log2 > 30) 
      return $output; 

     $count = 1 << $count_log2; 

     $salt = substr($setting, 4, 8); 
     if (strlen($salt) != 8) 
      return $output; 

     # We're kind of forced to use MD5 here since it's the only 
     # cryptographic primitive available in all versions of PHP 
     # currently in use. To implement our own low-level crypto 
     # in PHP would result in much worse performance and 
     # consequently in lower iteration counts and hashes that are 
     # quicker to crack (by non-PHP code). 
     if (PHP_VERSION >= '5') { 
      $hash = md5($salt . $password, TRUE); 
      do { 
       $hash = md5($hash . $password, TRUE); 
      } while (--$count); 
     } else { 
      $hash = pack('H*', md5($salt . $password)); 
      do { 
       $hash = pack('H*', md5($hash . $password)); 
      } while (--$count); 
     } 

     $output = substr($setting, 0, 12); 
     $output .= $this->encode64($hash, 16); 

     return $output; 
    } 

    function gensalt_extended($input) 
    { 
     $count_log2 = min($this->iteration_count_log2 + 8, 24); 
     # This should be odd to not reveal weak DES keys, and the 
     # maximum valid value is (2**24 - 1) which is odd anyway. 
     $count = (1 << $count_log2) - 1; 

     $output = '_'; 
     $output .= $this->itoa64[$count & 0x3f]; 
     $output .= $this->itoa64[($count >> 6) & 0x3f]; 
     $output .= $this->itoa64[($count >> 12) & 0x3f]; 
     $output .= $this->itoa64[($count >> 18) & 0x3f]; 

     $output .= $this->encode64($input, 3); 

     return $output; 
    } 

    function gensalt_blowfish($input) 
    { 
     # This one needs to use a different order of characters and a 
     # different encoding scheme from the one in encode64() above. 
     # We care because the last character in our encoded string will 
     # only represent 2 bits. While two known implementations of 
     # bcrypt will happily accept and correct a salt string which 
     # has the 4 unused bits set to non-zero, we do not want to take 
     # chances and we also do not want to waste an additional byte 
     # of entropy. 
     $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 

     $output = '$2a$'; 
     $output .= chr(ord('0') + $this->iteration_count_log2/10); 
     $output .= chr(ord('0') + $this->iteration_count_log2 % 10); 
     $output .= '$'; 

     $i = 0; 
     do { 
      $c1 = ord($input[$i++]); 
      $output .= $itoa64[$c1 >> 2]; 
      $c1 = ($c1 & 0x03) << 4; 
      if ($i >= 16) { 
       $output .= $itoa64[$c1]; 
       break; 
      } 

      $c2 = ord($input[$i++]); 
      $c1 |= $c2 >> 4; 
      $output .= $itoa64[$c1]; 
      $c1 = ($c2 & 0x0f) << 2; 

      $c2 = ord($input[$i++]); 
      $c1 |= $c2 >> 6; 
      $output .= $itoa64[$c1]; 
      $output .= $itoa64[$c2 & 0x3f]; 
     } while (1); 

     return $output; 
    } 

    function HashPassword($password) 
    { 
     $random = ''; 

     if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) { 
      $random = $this->get_random_bytes(16); 
      $hash = 
       crypt($password, $this->gensalt_blowfish($random)); 
      if (strlen($hash) == 60) 
       return $hash; 
     } 

     if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) { 
      if (strlen($random) < 3) 
       $random = $this->get_random_bytes(3); 
      $hash = 
       crypt($password, $this->gensalt_extended($random)); 
      if (strlen($hash) == 20) 
       return $hash; 
     } 

     if (strlen($random) < 6) 
      $random = $this->get_random_bytes(6); 
     $hash = 
      $this->crypt_private($password, 
      $this->gensalt_private($random)); 
     if (strlen($hash) == 34) 
      return $hash; 

     # Returning '*' on error is safe here, but would _not_ be safe 
     # in a crypt(3)-like function used _both_ for generating new 
     # hashes and for validating passwords against existing hashes. 
     return '*'; 
    } 

    function CheckPassword($password, $stored_hash) 
    { 
     $hash = $this->crypt_private($password, $stored_hash); 
     if ($hash[0] == '*') 
      $hash = crypt($password, $stored_hash); 

     return $hash == $stored_hash; 
    } 
} 
+0

Почему нет? Не самый безопасный метод хеширования, но очень быстрый. (просто шутит ...) – soulmerge

ответ

7

Я не знаю точно, что вы просите. В комментарии прямо говорится, что он возвращает * об ошибке, поэтому это не «безопасный хешированный пароль», это указывает на то, что при попытке генерации хэша произошла ошибка. Странный выбор для возвращаемого значения, но это то, что есть.

+10

Предположительно, это вдохновляет '/ etc/passwd', где' * 'в поле пароля представляет собой учетную запись, которая не может быть зарегистрирована (из-за' * 'не является хешем для любого пароля). – bobince

+0

Именно из-за кода ошибки я начал задаваться вопросом, не понял ли я что-то. Невинный пользователь api теперь должен прочитать код заранее, чтобы понять, что ему нужно протестировать против '*'. Спасибо, что подтвердили. –

+0

@bobince +1 Это звучит как хорошая причина для этого нечетного кода. –

2

Причина, по которой * может быть возвращена при ошибке, заключается в том, что * не является допустимым значением хэша любого пароля. Поэтому, возвращая это значение, вы получите значение, которое, очевидно, не является реальным хеш-значением, и не может сравниться с другим хэшем.

+0

Если вы не рассматриваете его как ошибку. Если пользователь набрал пароль, который ведет к «*», и вы сохраните его как хэш внутри базы данных. Затем вы делаете то же самое для проверки '*' == '*', чтобы тест проходил. Если может произойти ошибка, полезно знать, что «*» следует рассматривать как ошибку. Если это невозможно, зачем планировать это на коде? Он должен быть, по крайней мере, документирован. Или, может, я ошибаюсь? (сегодня это будет не первый раз ...) – Savageman

+0

Согласен. Но в этом случае я ожидал бы «ложь», «нуль» или «». –

+0

@Savageman, обратите внимание на комментарий относительно 'crypt (3)' в образце кода. Причина, по которой это безопасно, состоит в том, что эта функция не делает хэши для сравнения, а только хэши для хранения. –

Смежные вопросы