¿Se puede utilizar PHP para almacenar datos?

Almacenamiento Seguro de Contraseñas PHP

Valoración: 4.62 (1361 votos)

En el mundo digital actual, la seguridad de la información de los usuarios es primordial. Uno de los datos más sensibles que manejamos son las contraseñas. Almacenarlas de forma insegura en una base de datos es una receta para el desastre en caso de una brecha de seguridad. Afortunadamente, PHP ofrece herramientas robustas para manejar este desafío de manera efectiva.

El error más común y peligroso es guardar las contraseñas directamente en texto plano. Si la base de datos es comprometida, todos los usuarios verían sus credenciales expuestas, lo que podría llevar a ataques de relleno de credenciales en otros servicios que usen la misma combinación de usuario y contraseña.

¿Cómo puedo cambiar la contraseña de una base de datos MySQL?
En la columna Acciones, al lado de la base de datos deseada, haz clic en el ícono de la rueda dentada. Se abre un menú desplegable. Selecciona Modificar contraseña e introduce una nueva contraseña. A continuación, haz clic en Guardar .

La solución correcta es hashing. El hashing es un proceso unidireccional que transforma una cadena de texto (la contraseña) en una cadena de longitud fija (el hash). Es computacionalmente inviable revertir un hash para obtener la contraseña original. Sin embargo, para verificar si una contraseña ingresada es correcta, generamos el hash de la contraseña ingresada y lo comparamos con el hash almacenado en la base de datos. Si coinciden, la contraseña es correcta.

Índice de Contenido

La Aproximación Moderna: `password_hash()` y `password_verify()`

Desde PHP 5.5, se introdujo la API de hashing de contraseñas, que es la forma recomendada y más segura de manejar contraseñas. Esta API se centra en dos funciones principales: password_hash() para crear el hash y password_verify() para verificarlo.

La belleza de password_hash() reside en que maneja automáticamente el proceso de salting y la elección de algoritmos criptográficamente seguros. El salting es añadir datos aleatorios (el "salt") a la contraseña antes de hashearla. Esto asegura que dos usuarios con la misma contraseña tengan hashes diferentes, lo que frustra los ataques de tablas arcoíris.

La sintaxis básica de password_hash() es:

password_hash(string $password, int $algo, array $options = [])

  • $password: La contraseña en texto plano a hashear.
  • $algo: La constante del algoritmo de hashing a usar. La opción más recomendada y por defecto es PASSWORD_DEFAULT, que actualmente usa el algoritmo Bcrypt. Esta constante se actualiza automáticamente con algoritmos más fuertes a medida que estén disponibles en futuras versiones de PHP, sin necesidad de cambiar tu código. Otra opción común es PASSWORD_BCRYPT, que especifica explícitamente el uso de Bcrypt.
  • $options (opcional): Un array de opciones. Por ejemplo, para Bcrypt, puedes especificar el 'costo' (la complejidad computacional del hashing). Un costo mayor es más seguro pero requiere más tiempo de procesamiento. El valor por defecto suele ser adecuado.

La función password_hash() devuelve el hash de la contraseña, incluyendo la sal y el identificador del algoritmo utilizado, todo codificado en una sola cadena.

Proceso de Registro con `password_hash()`

Veamos cómo se usa password_hash() en un flujo de registro típico, basándonos en el ejemplo proporcionado:

Primero, la conexión a la base de datos (dbconn.php):

<?php $db_host = "localhost"; $db_name = "secure_pass"; $db_pass = ""; // ¡En producción, usa una contraseña fuerte! $db_user = "root"; // ¡En producción, usa un usuario con menos privilegios! $conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name); if (!$conn) { die('Failed to connect with server: ' . mysqli_connect_error()); } ?>

El formulario HTML (signup_form.php o similar):

<form action="index.php" method="POST"> <label for="username">Username</label> <input type="text" name="username" required><br><br> <label for="password">Password</label> <input type="password" name="password" required><br><br> <input type="submit" name="submit" value="Registrar"> </form>

Y el script de procesamiento en PHP (index.php):

<?php include 'dbconn.php'; if (isset($_POST['submit'])) { // ¡Importante! Sanitizar y validar las entradas del usuario $username = mysqli_real_escape_string($conn, $_POST['username']); $pass = $_POST['password']; // La contraseña en texto plano ANTES de hashear // Generar el hash seguro de la contraseña $secure_pass = password_hash($pass, PASSWORD_BCRYPT); // Usando PASSWORD_BCRYPT explícitamente // O mejor aún: $secure_pass = password_hash($pass, PASSWORD_DEFAULT); // Insertar el nombre de usuario y el hash en la base de datos // Usar consultas preparadas es aún más seguro contra inyección SQL $sql = "INSERT INTO login_tb (u_username, u_password) VALUES ('$username', '$secure_pass')"; if (mysqli_query($conn, $sql)) { echo "Usuario registrado exitosamente."; } else { echo "Error: " . $sql . "<br>" . mysqli_error($conn); } } // Incluir el formulario de registro si no se ha enviado include 'signup_form.php'; mysqli_close($conn); ?>

Como se ve en el ejemplo, en lugar de guardar $pass directamente, guardamos $secure_pass, que es el resultado de password_hash(). El campo u_password en la base de datos debe ser capaz de almacenar una cadena relativamente larga (por ejemplo, 255 caracteres o más), ya que los hashes con sal y algoritmo incluido pueden ser extensos.

Proceso de Inicio de Sesión con `password_verify()`

El proceso de verificación durante el inicio de sesión es donde entra password_verify(). Esta función toma la contraseña ingresada por el usuario (en texto plano) y el hash almacenado en la base de datos, y determina si la contraseña coincide con el hash. La sintaxis es:

password_verify(string $password, string $hash)

  • $password: La contraseña en texto plano ingresada por el usuario en el formulario de login.
  • $hash: El hash recuperado de la base de datos para el usuario que intenta iniciar sesión.

La función password_verify() retorna true si la contraseña coincide con el hash y false en caso contrario. Internamente, extrae la sal y el algoritmo del hash almacenado y usa esa información para hashear la contraseña ingresada, y luego compara el hash resultante con el hash almacenado. Esto es crucial para la seguridad.

Ejemplo de script de login (simplificado):

<?php include 'dbconn.php'; if (isset($_POST['login_submit'])) { $username = mysqli_real_escape_string($conn, $_POST['username']); $password_ingresada = $_POST['password']; // 1. Recuperar el hash de la base de datos para el usuario dado $sql = "SELECT u_password FROM login_tb WHERE u_username = '$username'"; $result = mysqli_query($conn, $sql); if ($result && mysqli_num_rows($result) == 1) { $row = mysqli_fetch_assoc($result); $hash_almacenado = $row['u_password']; // 2. Verificar la contraseña ingresada contra el hash almacenado if (password_verify($password_ingresada, $hash_almacenado)) { // Contraseña correcta! Iniciar sesión (ej. crear sesión) echo "Inicio de sesión exitoso!"; // Aquí iría la lógica para iniciar la sesión del usuario } else { // Contraseña incorrecta echo "Usuario o contraseña incorrectos."; } } else { // Usuario no encontrado echo "Usuario o contraseña incorrectos."; } } mysqli_close($conn); ?>

Este flujo es el estándar moderno y seguro para el manejo de contraseñas en PHP. La combinación de password_hash() y password_verify() es la mejor práctica recomendada.

Entendiendo `crypt()`: La Función Antigua

Antes de la API password_*, la función principal para hashear contraseñas en PHP era crypt(). Aunque todavía existe y funciona, es considerablemente más compleja de usar de forma segura y tiene limitaciones importantes en comparación con password_hash().

La sintaxis de crypt() es:

crypt(string $str, string $salt)

  • $str: La cadena (contraseña) a hashear.
  • $salt: Una cadena de sal. Este parámetro es crucial y su formato depende del algoritmo que se desee usar.

La función crypt() devuelve el hash de la cadena utilizando el algoritmo especificado por la sal proporcionada. Si no se proporciona una sal o la sal es inválida para el algoritmo deseado, el comportamiento puede ser impredecible o inseguro (por ejemplo, usar una sal débil o un algoritmo antiguo).

Algoritmos Soportados por `crypt()`

crypt() soporta varios algoritmos, cuya disponibilidad depende del sistema operativo y la configuración de PHP. PHP define constantes para identificar si un algoritmo específico es soportado:

  • CRYPT_STD_DES: Hashing basado en DES estándar. Requiere una sal de 2 caracteres. Considerado muy inseguro hoy en día debido a su debilidad y la capacidad de hardware moderno para romperlo rápidamente.
  • CRYPT_EXT_DES: Hashing basado en DES extendido. Usa una sal de 9 caracteres con formato específico. También considerado débil.
  • CRYPT_MD5: Hashing basado en MD5. Usa una sal de 12 caracteres que comienza con $1$. MD5 es criptográficamente roto (vulnerable a colisiones) y no debe usarse para hashear contraseñas.
  • CRYPT_BLOWFISH: Hashing basado en Blowfish (Bcrypt). Usa una sal que comienza con $2a$, $2x$ o $2y$, un parámetro de costo y 22 caracteres de sal. Este es uno de los algoritmos más fuertes soportados por crypt() y es la base de password_hash(PASSWORD_BCRYPT).
  • CRYPT_SHA_256: Hashing basado en SHA-256. Usa una sal que comienza con $5$. Soporta un parámetro de rondas. SHA-256 es una función hash criptográfica fuerte, pero el uso con crypt() requiere gestión manual de la sal y las rondas.
  • CRYPT_SHA_512: Hashing basado en SHA-512. Usa una sal que comienza con $6$. También soporta un parámetro de rondas. Similar a SHA-256 pero produce un hash más largo.

La principal dificultad con crypt() es que debes generar y gestionar tú mismo la sal, y asegurarte de que la sal tenga el formato correcto para el algoritmo que quieres usar. Además, para verificar una contraseña, necesitas extraer la sal del hash almacenado y pasársela a crypt() junto con la contraseña ingresada, y luego comparar los hashes resultantes. Esto es propenso a errores y más complejo que usar password_verify().

¿Cómo guardar el archivo en MySQL?
Guardar los resultados de MySQL en un archivo MySQL incluye una función integrada de salida a archivo como parte de la instrucción SELECT. Simplemente añadimos las palabras INTO OUTFILE, seguidas del nombre del archivo, al final de la instrucción SELECT . Por ejemplo: SELECT id, first_name, last_name FROM customer INTO OUTFILE '/temp/myoutput'.

crypt(), al igual que password_hash(), es una función unidireccional. No existe una función de "decrypt" para obtener la contraseña original a partir del hash.

`password_hash()` vs `crypt()`: ¿Cuál Usar?

La respuesta es clara para aplicaciones modernas: siempre usa la API password_* (principalmente password_hash() y password_verify()). Aquí hay una tabla comparativa:

Característicapassword_hash() / password_verify()crypt()
Facilidad de UsoMuy fácil. Maneja salting y formato automáticamente.Requiere generar y formatear la sal manualmente según el algoritmo.
Algoritmo RecomendadoPor defecto usa Bcrypt (con PASSWORD_DEFAULT), un algoritmo fuerte diseñado para hashing de contraseñas. Se actualiza automáticamente.Debes especificar el algoritmo a través del formato de la sal. Algoritmos antiguos (DES, MD5) son inseguros. Bcrypt es una opción, pero requiere generar la sal correctamente.
SaltingAutomático y seguro.Debe ser gestionado manualmente por el desarrollador. Omitir la sal o usar una sal débil es un riesgo de seguridad.
VerificaciónFunción dedicada password_verify() que maneja la extracción de sal y la comparación.Requiere extraer la sal del hash almacenado, llamar a crypt() con la contraseña ingresada y la sal extraída, y comparar los resultados manualmente.
Resistencia a FuturoPASSWORD_DEFAULT permite a PHP actualizar el algoritmo subyacente en el futuro sin cambiar tu código de hashing.Depende del algoritmo especificado por la sal. Si un algoritmo se vuelve obsoleto, debes cambiar tu código y posiblemente re-hashear contraseñas antiguas.
RecomendaciónRecomendado para todas las aplicaciones nuevas y existentes.Obsoleto para nuevos desarrollos de hashing de contraseñas. Solo considerar si necesitas compatibilidad con sistemas legacy muy antiguos (y aun así, evalúa migrar).

En resumen, password_hash() y password_verify() abstraen la complejidad de crypt(), proporcionando una interfaz más segura, fácil de usar y resistente al futuro para el manejo de contraseñas.

Preguntas Frecuentes sobre el Almacenamiento de Contraseñas

¿Es seguro usar MD5 o SHA-1 para hashear contraseñas?

No. MD5 y SHA-1 son funciones hash criptográficas diseñadas para verificar la integridad de los datos, no para almacenar contraseñas. Son muy rápidos de calcular y vulnerables a ataques de fuerza bruta y tablas arcoíris. Además, MD5 es vulnerable a colisiones. Nunca los uses para contraseñas.

¿Qué pasa si un atacante obtiene el hash de mi base de datos?

Si usas un algoritmo de hashing fuerte como Bcrypt (a través de password_hash()) con un costo adecuado, obtener el hash no le permite al atacante recuperar la contraseña original de forma práctica. Tendría que intentar hashear millones o billones de posibles contraseñas (ataque de fuerza bruta) hasta encontrar una cuyo hash coincida. La lentitud intencional de algoritmos como Bcrypt hace que este proceso sea extremadamente lento y costoso para el atacante, especialmente si las contraseñas son complejas y únicas.

¿Necesito almacenar la sal por separado en la base de datos?

No, si usas password_hash(). La función incluye automáticamente la sal y otra información necesaria (como el algoritmo y el costo) dentro de la cadena de hash resultante. password_verify() sabe cómo parsear esta cadena para extraer la sal y la información del algoritmo.

¿Qué tan largo debe ser el campo en la base de datos para almacenar el hash?

Para los hashes generados por password_hash() (usando Bcrypt), la longitud típica es de 60 caracteres. Sin embargo, como PASSWORD_DEFAULT podría cambiar a un algoritmo más largo en el futuro, es una buena práctica usar un tipo de datos que pueda almacenar cadenas más largas, como VARCHAR(255) o incluso TEXT, para estar preparado para futuras actualizaciones de PHP.

¿Debo usar consultas preparadas al insertar o seleccionar usuarios?

Sí, absolutamente. Aunque el hashing protege las contraseñas, usar consultas preparadas con PDO o MySQLi es esencial para prevenir ataques de inyección SQL al manejar el nombre de usuario y otros datos que provienen de la entrada del usuario. El ejemplo proporcionado usa mysqli_real_escape_string, que es una mejora, pero las consultas preparadas son generalmente consideradas más seguras y fáciles de usar correctamente.

¿Qué es el 'costo' en Bcrypt?

El costo es un parámetro logarítmico que determina cuántas iteraciones se realizan durante el proceso de hashing. Un costo mayor significa más trabajo computacional para hashear una contraseña, lo que ralentiza tanto el hashing inicial como la verificación, pero hace que los ataques de fuerza bruta sean exponencialmente más lentos. Debes elegir un costo que sea lo suficientemente alto como para ser seguro (ralentizando los ataques) pero no tan alto que cause problemas de rendimiento significativos en tu servidor (especialmente durante picos de inicio de sesión). El valor por defecto de password_hash() suele ser un buen punto de partida, pero puedes ajustarlo si es necesario.

Conclusión

La seguridad de las contraseñas de tus usuarios es una responsabilidad crítica. Almacenar contraseñas en texto plano o usar funciones de hashing débiles es un riesgo inaceptable. La API de hashing de contraseñas de PHP, con funciones como password_hash() y password_verify(), proporciona una forma segura, moderna y fácil de usar para proteger las credenciales. Al adoptar estas prácticas, no solo proteges a tus usuarios, sino que también construyes una aplicación más robusta y confiable.

Si quieres conocer otros artículos parecidos a Almacenamiento Seguro de Contraseñas PHP puedes visitar la categoría Seguridad.

Ivan

Soy un entusiasta de la tecnología con especialización en bases de datos, particularmente en MySQL. A través de mis tutoriales detallados, busco desmitificar los conceptos complejos y proporcionar soluciones prácticas a los desafíos cotidianos relacionados con la gestión de datos

Aprende mas sobre MySQL

Subir