29 oct 2025·8 min de lectura

Contraseñas protegidas por hash: seguridad sencilla de contraseñas para fundadores

Aprende qué son las contraseñas hasheadas, cómo debe construirse el inicio de sesión y las trampas comunes que hacen que fundadores guarden o envíen contraseñas por error.

Contraseñas protegidas por hash: seguridad sencilla de contraseñas para fundadores

Por qué la seguridad de contraseñas pasa a ser tu problema

Si administras una app con cuentas, estás en el negocio de las contraseñas. En el momento en que recoges una contraseña, estás guardando algo que los atacantes quieren y que los usuarios esperan que protejas.

Las apps pequeñas también reciben ataques. Bots escanean internet sin parar buscando objetivos fáciles: bases de datos expuestas, paneles de administración débiles, variables de entorno filtradas o logs de depuración que capturaron datos sensibles por accidente. Ser una etapa temprana no significa más seguro: normalmente significa menos personas que noten un problema rápido y menos horas para arreglarlo.

Si las contraseñas se filtran, el daño se extiende más allá de tu app. La gente reutiliza contraseñas, así que una filtración puede provocar accesos no autorizados a email, banca y otros servicios. También puedes perder la confianza de los usuarios de la noche a la mañana, lidiar con contracargos y pasar semanas limpiando en vez de construyendo.

La meta es simple: nunca deberías poder leer la contraseña de un usuario. Ni en tu base de datos, ni en un panel de administración, ni en un ticket de soporte, ni en un email. Por eso las apps almacenan contraseñas hasheadas: una versión unidireccional que se puede verificar al iniciar sesión pero no se puede convertir de nuevo en la contraseña original.

El manejo de contraseñas suele fallar de maneras muy ordinarias:

  • Almacenar contraseñas en texto plano, incluso “temporalmente” durante el registro
  • Enviar por email contraseñas a usuarios o al equipo para soporte
  • Loguear peticiones de registro o inicio de sesión (incluyendo contraseñas) durante la depuración
  • Copiar contraseñas en herramientas de analítica, hojas de cálculo o chats
  • Entregar un prototipo generado por IA que “funciona” pero omite salvaguardas básicas

Un escenario común: un fundador prueba un prototipo construido por IA y pide a los usuarios que “respondan con la contraseña que usaron” cuando falla el inicio de sesión. Aunque borres el email después, puede quedar en bandejas de entrada, copias de seguridad y herramientas de soporte. Arreglarlo normalmente implica rehacer el flujo de autenticación para que las contraseñas nunca salgan del lugar correcto.

Equipos como FixMyMess ven este patrón con frecuencia en builds apresurados. La victoria más rápida es eliminar cada vía por la que una contraseña pueda verse, copiarse o volver a reproducirse.

Qué significa “contraseñas hasheadas” en lenguaje sencillo

Una contraseña hasheada es lo que obtienes cuando pasas una contraseña por una función especial que la convierte en una cadena larga y con aspecto aleatorio. Piénsalo como poner ingredientes en una licuadora y obtener un batido. Puedes comparar batidos, pero no puedes sacar las fresas originales.

Así que cuando la gente dice “almacenamos contraseñas hasheadas”, significa que la base de datos guarda el batido, no los ingredientes.

Lo que se guarda suele ser un registro que incluye la salida del hash, el método usado y la aleatoriedad extra añadida durante el hashing (a menudo almacenada junto al hash).

El hashing es unidireccional a propósito. Si alguien roba tu base de datos, no debería poder convertir los valores almacenados en contraseñas reales. Incluso tú, como propietario de la app, no deberías poder leer las contraseñas de los usuarios.

Por eso enviar contraseñas por email es una señal de alarma grave. Si puedes enviarla, debes tenerla en forma legible en algún sitio.

Entonces, ¿cómo funciona el inicio de sesión si no almacenas la contraseña? Cuando un usuario inicia sesión, tu app hashea la contraseña que escribió usando el mismo método y luego compara ese resultado con el hash almacenado.

Si coinciden, la contraseña era correcta. Si no, se deniega el acceso. Nadie necesita “buscar” la contraseña.

Si heredaste una app generada por IA que almacena contraseñas en texto plano o las envía por email durante el registro, normalmente es rápido corregirlo y quitar una gran parte del riesgo antes del lanzamiento.

Hashing vs encriptación vs codificación (comparación rápida)

La gente las confunde porque todas “cambian” datos. Pero están hechas para trabajos distintos, y las contraseñas tienen una regla clara: no deberías poder recuperar la contraseña original desde lo que almacenas.

El hashing es unidireccional. La encriptación es reversible. La codificación es solo un cambio de formato.

  • Hashing: convierte una contraseña en una cadena que no se puede revertir. Al iniciar sesión, hasheas lo que puso el usuario y lo comparas con el hash almacenado.
  • Encriptación: cifra datos de una manera que puede revertirse con una clave. Si un atacante roba la clave, puede descifrar los datos.
  • Codificación (Base64, codificación de URL, etc.): facilita almacenar o transmitir datos. Cualquiera puede decodificarlo. No aporta seguridad.

Si tu sistema dice “podemos descifrarlo después”, ese es el modelo equivocado para contraseñas. La capacidad de descifrar contraseñas se convierte en una responsabilidad.

Dónde la encriptación sigue siendo útil

La encriptación sigue siendo importante, pero no para almacenar contraseñas. Úsala para cosas que tu app debe leer de nuevo más tarde, como claves API y tokens de acceso, datos sensibles de usuarios, copias de seguridad y datos que se mueven entre sistemas.

Una prueba simple: si tu app envía por email a los usuarios “tu contraseña es…”, significa que la almacenaste de forma reversible en algún lugar. Arréglalo antes del lanzamiento. Un sistema correcto no conoce la contraseña original después del registro.

Cómo deben funcionar el registro y el inicio de sesión (paso a paso)

Un sistema de inicio de sesión seguro se reduce a una regla: tu app no debería necesitar recordar la contraseña real del usuario después del momento en que la escribe.

Flujo de registro e inicio de sesión (versión segura)

La mayoría de las apps modernas siguen un patrón aburrido y confiable:

  • Registro: el usuario introduce una contraseña, tu servidor la hashea y solo guardas el hash.
  • Inicio de sesión: el usuario introduce una contraseña, tu servidor la hashea de la misma forma y la compara con el hash almacenado.
  • Resultado: si los hashes coinciden, el inicio de sesión procede. Si no, falla. Nada se desencripta.

Mantén los mensajes de error genéricos. Evita decir “email no encontrado” vs “contraseña incorrecta” porque eso ayuda a los atacantes a averiguar cuentas válidas.

Qué nunca debe aparecer en logs o analítica

Muchas brechas empiezan con logging “útil”. No registres secretos. Eso incluye contraseñas, hashes de contraseña, tokens de restablecimiento, enlaces mágicos, tokens de sesión, cabeceras de autorización y cuerpos completos de petición desde endpoints de autenticación.

Tras un inicio de sesión exitoso, no sigas enviando la contraseña por ahí. El servidor debe crear un token de sesión (una cadena aleatoria que prueba que el usuario ya inició sesión) y devolverlo a la app. La app usa ese token en futuras peticiones.

Si heredaste código de autenticación generado por IA, verifica que siga exactamente este flujo. Es común encontrar contraseñas registradas, almacenadas en texto plano o enviadas a analítica “para depurar”. Esas son correcciones de alto riesgo y normalmente rápidas de hacer.

Elegir el método correcto de hashing de contraseñas

Endurece tu prototipo de IA
FixMyMess repara código generado por IA para que sea seguro y listo para producción.

Si tu base de datos se filtra, los atacantes no deberían poder convertir rápidamente hashes robados en contraseñas reales.

La idea clave es “hashing lento”. Un buen hash de contraseñas es deliberadamente caro de calcular. Tu servidor comprueba un inicio de sesión a la vez; un atacante prueba miles de millones de conjeturas. El hashing lento perjudica mucho más al atacante que a ti.

Buenas opciones (y por qué son comunes)

La mayoría de los equipos elige un método de hashing conocido:

  • Argon2id: un estándar moderno diseñado para ser difícil de romper con GPUs.
  • bcrypt: más antiguo, muy usado y bien comprendido.
  • scrypt: también exige mucha memoria y es una opción razonable cuando no está disponible Argon2.

Si no eres experto en seguridad, un valor sensato por defecto es: elige Argon2id si está soportado; de lo contrario bcrypt. Empieza con las configuraciones recomendadas de la librería y solo ajústalas si tienes una razón clara.

Por qué SHA-256 es un mal predeterminado para contraseñas

Hashes de propósito general como SHA-256 son rápidos por diseño. Eso es genial para comprobaciones de archivos y firmas, pero peligroso para contraseñas. Los hashes rápidos permiten a los atacantes probar un número enorme de conjeturas por segundo. Incluso si “lo hasheas dos veces”, normalmente sigue siendo demasiado rápido.

Una regla práctica: no inventes tu propio sistema. Usa una librería de autenticación conocida o la función de confianza del framework.

Salts y peppers sin jerga

Si solo recuerdas una cosa sobre contraseñas hasheadas, que sea esta: la misma contraseña no debería convertirse en el mismo valor almacenado para cada usuario.

Salt: un “extra” único por usuario

Una salt es un valor aleatorio generado para cada cuenta cuando se establece la contraseña. El sistema mezcla la salt con la contraseña antes de hashear y luego guarda la salt junto al hash.

Esto importa en una filtración de base de datos. Sin salts, dos usuarios que escogieron “Summer2026!” terminarían con el mismo hash. Los atacantes pueden detectar repeticiones y adivinar contraseñas comunes más rápido.

Con salts únicas, aunque 1.000 personas reutilicen la misma contraseña, sus hashes almacenados se verán diferentes.

Pepper: un secreto guardado fuera de la base de datos

Un pepper es otro valor mezclado antes del hashing, pero a diferencia de la salt, no se guarda en la base de datos. Vive en la configuración del servidor para que siga siendo secreto incluso si la base de datos se expone.

Los peppers solo son útiles si puedes proteger y rotar correctamente los secretos de la app. Son una capa adicional, no un sustituto de hacer lo básico bien.

Una lista rápida para fundadores:

  • La salt debe ser aleatoria y única por usuario, generada automáticamente.
  • La salt puede almacenarse en la base de datos junto al hash.
  • El pepper, si se usa, debe guardarse fuera de la base de datos y tratarse como un secreto de alto valor.

Un problema común en código de autenticación generado por IA es una “salt” hardcodeada compartida por todos los usuarios. Parece salado, pero anula el propósito.

Errores comunes que crean deuda de seguridad al instante

La mayoría de los desastres con contraseñas no son “magia de hackers”. Empiezan como atajos en builds apresurados y luego se consolidan en el producto y las costumbres de soporte.

La mayor señal de alarma es almacenar contraseñas en texto plano en cualquier sitio: base de datos, hojas de cálculo, paneles de administración, notas o tablas “temporales”. Si alguien puede leerlas, acabarán filtrándose.

Otro error común es enviar contraseñas por email o DM, aunque las llames temporales. Las bandejas de entrada se reenvían, se comparten y se sincronizan entre dispositivos. Además, acostumbra a los usuarios a aceptar mensajes de riesgo que parecen de tu marca.

La depuración puede crear silenciosamente el mismo problema. Si tu app registra cuerpos completos de petición durante registro o inicio de sesión, podrías estar guardando contraseñas en logs de servidor, herramientas de analítica, informes de fallos o tickets de soporte. Esos sistemas suelen tener acceso amplio dentro del equipo.

Patrones que crean deuda de seguridad rápido incluyen almacenamiento legible de contraseñas, funciones de “admin puede ver la contraseña del usuario”, imprimir contraseñas en logs durante pruebas, copiar contraseñas en chats de soporte y pedir a usuarios que te envíen su contraseña “para verificarla”.

Una regla sencilla ayuda: tu app nunca debería poder mostrar la contraseña original de un usuario, porque solo debería almacenar hashes de contraseñas.

Si necesitas apoyar usuarios, apunta a herramientas de soporte que no requieran acceso a contraseñas: enlaces de restablecimiento, logs y tickets redactados, y acciones como revocar sesiones o reenviar un restablecimiento.

Restablecimientos de contraseña y protecciones básicas que realmente funcionan

Arregla el manejo de contraseñas rápido
La mayoría de los proyectos se arreglan en 48–72 horas después de una auditoría gratuita.

Un flujo de “olvidé mi contraseña” no trata de enviar la contraseña antigua de alguien. Con contraseñas hasheadas, el servidor solo puede verificar una contraseña, no recuperarla.

Un restablecimiento seguro usa un token de un solo uso y con tiempo limitado. El usuario prueba que controla el email (o teléfono) vinculado a la cuenta y luego establece una nueva contraseña.

Un flujo estándar es así:

  • El usuario introduce su email y solicita un restablecimiento.
  • Tu app genera un token aleatorio, guarda solo una versión hasheada de ese token y establece una expiración (por ejemplo 15–60 minutos).
  • Envías un mensaje de restablecimiento que contiene el token.
  • Cuando el usuario lo abre, verificas el token y la expiración, y le permites establecer una nueva contraseña.
  • Invalidas el token tras su uso y, opcionalmente, cierras otras sesiones.

No reveles si existe o no una cuenta. Muestra siempre el mismo mensaje, por ejemplo: “Si existe una cuenta para ese email, recibirás un enlace de restablecimiento.” Esto evita ataques de enumeración de cuentas.

Las protecciones básicas no necesitan ser sofisticadas para funcionar. Limitar la tasa de peticiones, bloqueos cortos tras fallos repetidos, alertas a usuarios por restablecimientos o inicios nuevos y expiración de sesión tras un restablecimiento detienen mucho abuso.

En apps generadas por IA, un bug común en restablecimientos es un token que nunca expira, que puede reutilizarse o que queda registrado en texto plano. Pequeñas correcciones aquí pueden prevenir un incidente mayor.

Ejemplo realista: arreglar una autenticación rota antes del lanzamiento

Un fundador en solitario lanza una app generada por IA con un formulario de login que “funciona”. Bajo el capó, la tabla de la base de datos tiene una columna password que guarda exactamente el texto de la contraseña. La app incluso envía la contraseña por email durante el registro “para que los usuarios no la olviden”. Parece útil, pero es peligroso.

El riesgo aparece de formas normales. Un usuario pide soporte: “¿Me puedes decir mi contraseña?” Alguien abre un panel de administración y ve contraseñas en texto plano. O una copia de la base de datos se comparte con un contratista y de repente más gente tiene acceso a contraseñas reales. A veces es peor: las contraseñas aparecen impresas en logs de servidor durante la depuración.

La solución no es “ocultar la columna”. La solución es cambiar a contraseñas hasheadas para que el sistema solo almacene una huella unidireccional de la contraseña. En la práctica, eso significa añadir un campo password_hash, actualizar el registro para hashear antes de guardar, actualizar el inicio de sesión para verificar el hash, eliminar cualquier código que envíe contraseñas por email y limpiar logs que puedan contenerlas.

Manejar usuarios existentes es la parte complicada. La mayoría de equipos elige una de estas opciones:

  • Restablecimiento forzado: marcar todas las cuentas como necesitadas de restablecimiento, enviar un enlace y dejar de aceptar las contraseñas antiguas.
  • Actualización gradual: mantener el campo antiguo temporalmente y, cuando un usuario inicie sesión con éxito, reemplazarlo por un hash y borrar la contraseña en texto plano.
  • Híbrido: forzar restablecimientos para administradores y cuentas de alto riesgo, y actualizar al resto en el próximo inicio de sesión.

Después de elegir, prueba flujos reales de usuario (registro, inicio, restablecimiento, cierre de sesión) y confirma que nada se rompa.

Un estado final seguro se ve así:

  • No hay contraseñas en texto plano en ningún lado (base de datos, logs, vistas de admin, emails).
  • Las cuentas nuevas almacenan solo hashes y el inicio de sesión verifica de forma segura.
  • El flujo de restablecimiento funciona y no revela si un email existe.
  • Los datos antiguos de contraseñas se borran tras la migración.
  • Hay protecciones básicas (limitación de tasa, bloqueos y mensajes de error seguros).

Lista rápida de verificación antes del lanzamiento

Reparar inicio de sesión roto
Si los usuarios no pueden iniciar sesión, diagnosticamos y parcheamos la lógica de autenticación rápidamente.

Haz estas comprobaciones una vez antes de tus primeros usuarios reales. Capturan la mayoría de los problemas de contraseñas que se “arreglarían más tarde”, especialmente en apps construidas rápido o con herramientas de IA.

  • Base de datos: confirma que solo almacenas hashes de contraseñas (más una salt única). No debe haber backups, tablas “temporales” ni columnas sobrantes que guarden contraseñas en texto plano.
  • Logs e informes de errores: provoca un inicio de sesión fallido y un error de registro a propósito, luego inspecciona los logs de petición y los informes de fallos. Los campos de contraseña deben estar redactados.
  • Restablecimiento de contraseña: prueba el flujo completo. Los tokens deben expirar, ser de un solo uso e invalidarse tras el cambio de contraseña.
  • Herramientas de administración y soporte: asegúrate de que ninguna UI interna pueda revelar o exportar contraseñas. Si una pantalla de soporte muestra “contraseña actual”, trátalo como un bug serio.
  • Secretos y claves: escanea tu repositorio y configuración de despliegue en busca de claves API expuestas, URLs de bases de datos o secretos JWT. Deben vivir en variables de entorno, no en código ni en dashboards compartidos con contratistas.

Una forma práctica de empezar: busca en tu código password, reset, token, log y debug. Si ves que la app almacena un valor de contraseña, lo envía por email o lo registra, trátalo como un bloqueo para el lanzamiento.

Si la autenticación parece frágil (restablecimientos rotos, secretos expuestos, comportamiento extraño de sesiones), una auditoría focalizada puede evitarte un incidente el día del lanzamiento.

Siguientes pasos si tu app fue construida rápido (o por IA)

Si tu app se construyó rápido, asume que la autenticación y el manejo de contraseñas pueden estar mal hasta que se demuestre lo contrario. Esto es especialmente cierto con código generado por IA, donde es común encontrar contraseñas registradas, almacenadas en texto plano o copiadas en emails durante las "pruebas". Incluso si ya usas contraseñas hasheadas, errores alrededor de restablecimientos, sesiones y secretos pueden seguir poniendo en riesgo a los usuarios.

Un buen momento para una revisión externa es justo antes de invitar usuarios reales, conectar pagos o lanzar en un dominio público. Otro disparador es código de autenticación desordenado: múltiples rutas de login, criptografía casera o ‘backdoors’ de admin temporales que nunca se eliminaron.

Una revisión práctica de seguridad normalmente cubre:

  • Registro, inicio de sesión, sesiones, cierre y restablecimiento de contraseña
  • Escaneo de secretos (claves API, URLs de bases de datos, tokens en commits accidentales)
  • Endurecimiento (limitación de tasa, bloqueos, mensajes de error seguros, protección CSRF si aplica)
  • Manejo de datos (qué se registra, envía por email o guarda en analítica)
  • Una lista corta de arreglos ordenada por riesgo y esfuerzo

Si heredaste un prototipo generado por IA roto, FixMyMess en fixmymess.ai se enfoca en diagnosticar la base de código y arreglar problemas como almacenamiento inseguro de contraseñas, secretos expuestos y flujos de autenticación frágiles para que la app esté lista para producción.

Los tiempos pueden ser más rápidos de lo que piensas. Muchos proyectos pueden diagnosticarse y arreglarse en 48–72 horas, dependiendo de lo enmarañado que esté el código de autenticación y de si hay problemas ocultos como secretos expuestos o comprobaciones de autorización defectuosas.

Si dudas entre parchear o reconstruir, usa una regla simple: parchea cuando la estructura de la app esté sana, reconstruye cuando la base sea inestable.

Señales de que reconstruir es la opción más segura:

  • La lógica de autenticación está dispersa en muchos archivos con reglas inconsistentes
  • Los flujos de restablecimiento son personalizados y difíciles de razonar
  • Secretos están embebidos en el frontend o en el historial del repo
  • No hay separación clara entre usuarios, roles y permisos
  • Las correcciones siguen causando nuevos bugs en partes no relacionadas de la app

Preguntas Frecuentes

¿Por qué la seguridad de contraseñas es mi problema si solo manejo una app pequeña?

Estás recopilando un secreto que los atacantes buscan activamente y que los usuarios esperan que protejas. Si se filtra, personas que reutilizaron esa contraseña pueden verse afectadas fuera de tu app, y tú tendrás que dedicar tiempo a limpiar en lugar de a construir.

¿Qué significa realmente “contraseñas hasheadas”?

El hashing convierte la contraseña en una “huella” unidireccional que puedes comparar al iniciar sesión, pero que no puedes convertir de nuevo en la contraseña original. La base de datos almacena la huella, no la contraseña real, así que ni siquiera tú puedes leerla.

¿No puedo simplemente encriptar las contraseñas en lugar de hashearlas?

No. La encriptación es reversible si alguien consigue la clave, lo que significa que las contraseñas originales podrían recuperarse si la clave se filtra. Las contraseñas deben hashearse con una función diseñada para ello, de modo que no haya nada que desencriptar después.

¿Cuál es un buen método de hashing por defecto para una app web típica?

Usa Argon2id si tu stack lo soporta; si no, bcrypt es una buena opción por defecto. El objetivo principal es usar un hash de contraseñas deliberadamente lento y diseñado para ese propósito, de modo que los hashes robados sean caros de descifrar.

¿Necesito salts o peppers, y cuál es la diferencia?

Una salt es un valor aleatorio único que se guarda con cada hash de contraseña para que contraseñas idénticas no produzcan el mismo valor almacenado. Un pepper es un secreto adicional guardado fuera de la base de datos; puede ayudar, pero solo si gestionas y rotas bien esos secretos.

¿Qué hago si mi app registró contraseñas accidentalmente durante la depuración?

Trátalo como un problema crítico que impide el lanzamiento. Borra o redacta los logs relevantes, rota cualquier token que pueda haber quedado expuesto y actualiza el logging para que los cuerpos de petición y campos sensibles nunca se almacenen.

¿Mi función de 'olvidé mi contraseña' debería enviar a los usuarios su contraseña antigua por email?

No. Un restablecimiento correcto envía un enlace o código de un solo uso y con límite de tiempo para que el usuario establezca una nueva contraseña, porque no puedes recuperar la antigua si solo almacenas hashes.

Mi base de datos ya tiene contraseñas en texto plano: ¿cuál es la forma más segura de arreglarlo?

No intentes “ocultar” la columna y seguir adelante. Cambia a almacenar hash de contraseña, elimina cualquier código que envíe o muestre contraseñas y migra usuarios forzando un restablecimiento o actualizando a hashes cuando inicien sesión con éxito.

¿Está bien que un admin vea la contraseña de un usuario para soporte?

No. Si una pantalla de administración puede revelar la contraseña de un usuario, significa que la estás almacenando en forma legible en algún lugar, lo cual es arriesgado. El soporte debe basarse en restablecimientos, revocaciones de sesión y recuperación de cuenta segura.

Construí mi app con una herramienta de IA: ¿qué problemas de contraseña/autenticación debo esperar?

Asume que la autenticación está mal hasta que se demuestre lo contrario: builds apresurados suelen guardar contraseñas, filtrar secretos o mal usar tokens de restablecimiento. Si quieres una revisión rápida y práctica, FixMyMess puede auditar tu código y arreglar el manejo inseguro de contraseñas y flujos de autenticación frágiles para que puedas lanzar con menos sorpresas.