2010-04-07 9 views
11

phpass es un hash 'framework' ampliamente utilizado.
¿Es una buena práctica para salar la contraseña al descubierto antes de dárselo a PasswordHash (v0.2), al igual que ?:¿La sal contenida en un phpass hash o necesita sal de su entrada?

$dynamicSalt = $record['salt']; 
$staticSalt = 'i5ininsfj5lt4hbfduk54fjbhoxc80sdf'; 
$plainPassword = $_POST['password']; 
$password  = $plainPassword . $dynamicSalt . $staticSalt; 

$passwordHash = new PasswordHash(8, false); 
$storedPassword = $passwordHash->HashPassword($password); 

Para tener una referencia a la clase 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; 
    } 
} 

Respuesta

32

Esta es una respuesta del propio autor original:

Además del hash real, phpass genera de forma transparente sales de azar cuando es ordenado una nueva contraseña o frase de contraseña, y que codifica el tipo de hash , la sal , y la iteración de estiramiento de la contraseña cuenta en el "cadena de codificación hash" que devuelve. Cuando phpass autentica una contraseña o frase de contraseña frente a un hash almacenado, de forma similar, transparentemente extrae y usa el identificador de tipo hash, el salt y la iteración para contar la "cadena de codificación hash". Por lo tanto, no necesita molestar con la salazón y el estiramiento por su cuenta - phpass se ocupa de estos para usted .

En pocas palabras: no tiene sentido saltear su contraseña antes de "phpassing".

1

Usted realmente no necesita dos sales (es decir, la sal estática es redundante, la sal dinámica es abundante): el objetivo principal de una sal es evitar ataques de tabla arcoiris si los hashes alguna vez son adquiridos por una parte maliciosa, y el motivo de sales dinámicas es prevenir aún más rainbo caso especial w generación de tabla de romper todas las contraseñas simultáneamente.

Aparte de eso, no puede doler la sal independientemente de si la biblioteca tiene o no la salazón incorporada (aunque a menos que esté pasando más información que solo el elemento que se va a aplicar, realmente no tiene algo que usar como sal dinámica, por lo que es probable que no sea sal para usted si no es ya obvio que lo es).

+0

'(aunque a menos que le esté pasando más información que solo el elemento que se va a codificar, en realidad no tiene nada que usar como una sal dinámica, por lo que es probable que no le salga si no está ya es obvio que sí) .' Todavía estoy tratando de analizar esta oración ... :) ¿Podrías explicar a qué te refieres? –

+0

Una sal doble puede ser legítima en algunos casos. Por ejemplo, si le preocupa que una de las sales pueda ser obtenida por un atacante. Por ejemplo, si se almacena 1 sal en la base de datos, entonces se podría obtener con la inyección sql, otra sal podría almacenarse en un archivo plano, que es más difícil de obtener. Un hash de contraseña no se puede romper hasta que se obtengan todas las sales, una vez que se obtienen las sales es trivial para romper con un ataque de diccionario como John the Ripper. – rook

+0

@Exception: suponiendo que desea el beneficio de una sal dinámica, necesita alguna información que no esté relacionada con el elemento que se está procesando en realidad, pero que se almacena para cuando se desee volver a comparar. @TheRook, las sales no están destinadas a ser elementos ocultos. Si alguien puede obtener el hash en primer lugar, realmente tratar de ocultar la sal no te va a comprar mucho; deberías haber ocultado el hash mejor. Tenga en cuenta que si alguien puede leer su código para saber cómo deberían incorporar la sal en primer lugar, puede leer cualquier archivo plano. – Amber

Cuestiones relacionadas