Definición
Un hash es el resultado de pasar un dato por una función matemática que produce una cadena de longitud fija. Da igual que le des una palabra o un fichero de un giga: el resultado siempre mide lo mismo.
Tres propiedades lo definen:
Determinista. El mismo dato de entrada da siempre exactamente el mismo hash.
De un solo sentido. Del hash no se puede volver al dato original. No es cifrado: el cifrado se puede deshacer con la clave, un hash no se deshace nunca.
Sensible al mínimo cambio. Cambia una letra del texto y el hash resultante no se parece en nada al anterior.
Se usa para tres cosas: guardar contraseñas, comprobar que un fichero no se ha alterado, e identificar contenido de forma compacta.
El caso de las contraseñas
Es el uso más importante y donde más se mete la pata.
Una contraseña nunca se guarda tal cual. Se guarda su hash. Cuando alguien entra, se calcula el hash de lo que ha escrito y se compara con el guardado. Si tu base de datos se filtra, el atacante tiene hashes, no contraseñas.
Pero hay dos problemas que el hash a secas no resuelve:
Contraseñas iguales dan hashes iguales. Si dos usuarios tienen la misma contraseña, se ve en la base. Y existen tablas gigantes con los hashes de millones de contraseñas comunes ya calculados. La solución es la sal: un valor aleatorio distinto para cada usuario que se añade antes de calcular el hash.
Los hashes normales son demasiado rápidos. SHA-256 está diseñado para ser veloz, y eso es exactamente lo que quiere un atacante: con una tarjeta gráfica se prueban miles de millones por segundo. Para contraseñas hace falta una función deliberadamente lenta.
Por eso, para contraseñas se usan bcrypt, scrypt o Argon2, no SHA-256. Llevan la sal incorporada y tienen un factor de coste ajustable: cuando el hardware mejore, subes el coste y siguen siendo lentas.
$hash = password_hash($clave, PASSWORD_BCRYPT); // al registrar
password_verify($clave, $hash); // al entrar
Ejemplo práctico
En mis proyectos hay tres usos de hash bastante distintos, y sirven para ver la variedad.
Contraseñas. El acceso al panel de AEWorks guarda un hash bcrypt en un fichero de configuración fuera del repositorio. Ni yo puedo leer la contraseña; solo comprobar si la que se escribe coincide.
Anonimizar sin perder la utilidad. El asistente público de la web tiene topes por IP, así que necesita distinguir visitantes. Pero el aviso legal promete no guardar datos personales, y una IP lo es. La solución: se guarda un hash de la IP con una sal que cambia cada día. Sirve para contar peticiones dentro del día, y pasadas 24 horas ni siquiera yo puedo reconstruir de qué IP venía.
Detectar cambios. Al comparar la copia local de una web con lo que hay en el servidor, se calcula el hash de cada fichero en los dos lados. Los que coinciden están igual; los que no, han cambiado. Con miles de ficheros es la única forma práctica de saberlo.
Un detalle de ese último caso que da la lata: el comando de hash de algunos entornos marca los ficheros binarios con un asterisco delante de la ruta. Si comparas las dos listas sin normalizar eso, no casa ni un solo fichero y parece que todo ha cambiado.
Errores comunes
- Guardar contraseñas en claro. Sigue pasando. Es indefendible.
- Usar SHA-256 o MD5 para contraseñas. Demasiado rápidos. MD5 además está roto desde hace años.
- Inventarse el esquema. "SHA-256 aplicado tres veces con mi sal" no es más seguro: es más frágil. Usa la función que tu lenguaje ya trae.
- Reutilizar la misma sal. Debe ser distinta por usuario y aleatoria.
- Comparar hashes con
==. Para datos sensibles hay que usar una comparación de tiempo constante, o el tiempo que tarda la comparación filtra información. - Confundir hash con cifrado. Si necesitas recuperar el valor original —el IBAN de un cliente, por ejemplo—, eso se cifra, no se hashea.
Cuándo usarlo
Hash cuando solo necesitas comprobar si algo coincide sin guardar el original: contraseñas, integridad de ficheros, identificadores anónimos.
Cifrado cuando necesitas recuperar el valor: credenciales de terceros que hay que usar, datos bancarios, documentos.
La pregunta que lo decide: ¿necesito volver a leer este dato alguna vez? Si sí, cifrado. Si solo necesito comprobar, hash.