Burdjia

Mejorando la sal en la protección de datos

Aunque el tema que voy a tratar es técnico, no hace falta saber mucho.  De hecho creo que incluso alguien que no sepa (todavía) cómo se maneja el tema de las claves de acceso y su protección puede entenderlo.  Aun así, para que todos estemos al mismo nivel, deberíais echar un vistazo al artículo Entendiendo las Funciones Hash y Cómo Mantener las Contraseñas Seguras, que explica perfectamente el tema y no precisa ningún conocimiento previo.  No hace falta que lo leas si controlas el tema.

Vale, ¿estamos todos?  Pues al turrón.

La cuestión es que, tras darle vueltas y como uno es rarito, he decidido hacer mi propio gestor de contenidos.  Que sí, que los hay muy buenos, que lo sé, que los conozco, pero es que cuatro megas (por poner el ejemplo de CodeIgniter, y que ni siquiera es un gestor de contenidos completo) para lo que necesito me parece demasiado.  Evidentemente mi gestor de contenidos ha de ser, también, mínimamente seguro.  Mi idea es confiar en el servidor todo lo que se pueda, pero el tema del acceso y las contraseñas tengo que manejarlo yo.  Buscando información encontré el artículo anteriormente mencionado (a partir de ahora lo llamaré "El Artículo").  Leyéndolo me di cuenta de que ya hacía algo bien, aunque fuera un poco por casualidad.  Esta no es la primera vez que me veo en el brete de almacenar claves de acceso.  Desde hace años uso un sistema consistente en codificar la clave junto con otro dato del usuario, normalmente su dirección de correo, usando bien MD5, bien SHA1, según me diera.  La razón era que si había dos usuarios que tuvieran la misma clave, en la base de datos no aparecieran con el mismo hash, y sin saberlo estaba añadiendo sal a la ensalada.  Bien por mi.  Cuando supe lo que era la sal, me di cuenta de la tremenda serendipia del caso, pero no me puse a analizarlo en profundidad.  Cuando me puse a trabajar en Ágora 2.0 (que publicaré, lo prometo) y en el gestor de contenidos, decidí que era buen momento para machacar el tema.  Y lo hice, ¡vaya si lo hice!  Hasta el punto de encontrar el que, posiblemente, sea el mejor método hasta la fecha para elegir la sal a usar.  Si El Artículo propone un método que con un buen ordenador necesitaría siete años (7) para crear la tabla rainbow, mi sistema necesitaría al menos catorce (14).  Y todo ello manteniéndolo KISS.  No está mal, ¿verdad?

La cuestión es sencilla.  Para poder usar la sal, hay que saber cuál es esa sal.  Es obvio, lo sé, pero es importante tenerlo en cuenta.  Tenemos dos opciones para saber qué sal usar:  calcularla o almacenarla.  En el caso de El Artículo tenemos que almacenarla, ya que obtenemos esa sal de forma aleatoria, usando mt_rand.  Si hacemos esto y un cracker nos roba la base de datos, tardará siete años por usuario (o menos, si va comprobando los resultados según los va calculando) en obtener su premio.  Sin embargo, si esa sal no está almacenada en la base de datos sino que tiene que calcularla, tardará un poco más.  ¿Qué tal si tarda en calcular esa sal tanto como en calcular el propio hash?  Pues que se duplica el tiempo.  ¿Y cómo hacerlo?  Pues haciendo que la sal sea, precisamente, un hash.  Pero, ¿cómo hacer que el piratón tarde lo mismo en calcular la sal, esto son siete años en nuestro caso?  Pues haciendo que la sal se calcule a partir de la propia clave de acceso.  Al no conocer la clave, deberá calcular una sal por cada posible clave y usar dicha sal para calcular el hash de la propia clave.  Es decir: calcular dos hash por clave.  Asunto resuelto.  ¿No?  ¿Os habéis perdido?  No problemo.  A ver qué os parece esto:


    public function ObtieneHash ($Nombre, $Clave)
    {
    # Generamos la sal.
      $Sal = substr (sha1 ($Nombre.$Clave), 0, 22);
      $Sal = substr (crypt ($Sal, '$2y$10$'.$Sal), 29, 22);
    # Obtenemos la codificación de la contraseña.
      return substr (crypt ($Clave, '$2y$10$'.$Sal), 29, 32);
    }

¿Está más claro?  Primero obtenemos una sal inicial a través de unir el nombre y la clave (para evitar que usuarios diferentes con la misma clave tengan la misma sal y devuelvan el mismo hash).  Luego re-procesamos la sal con un coste de 10 (es decir, que hace 1024 iteraciones), y finalmente usamos esta sal para calcular el hash de la clave, de nuevo con un coste de 10.  Además la función devuelve sólo el hash y no la sal, como hace crypt, ya que no necesitamos almacenarla.  Cuando el usuario intenta acceder, usamos la clave que nos dé de la misma forma.  La tabla rainbow, por tanto, necesitará el doble de tiempo para generarse, multiplicando el tiempo por el número de usuarios, ya que al incluir el nombre en la sal, la misma clave dará sales diferentes a diferentes usuarios.

Realmente desconozco si esto ya era conocido, pero lo cierto es que no lo he encontrado en ningún otro sitio.  De todas formas, podéis usarlo si lo deseáis;  y también podéis ponerlo a prueba, y si encontráis algún problema no dudéis en hacérmelo saber.

Escribe tu comentario

HTML permitido: cite, code, dd, dl, dt, em, li, ol, p, q, small, strong, ul