Implementando la Autenticación de Usuario de la Manera Correcta

Escribir sobre Passport.js el otro día me hizo pensar en cómo funciona realmente la autenticación y, lo que es más importante, en cuántas formas puede salir mal. El ingenuo...

Introducción

Escribiendo sobre Pasaporte.js el otro día me hizo pensar en cómo funciona realmente la autenticación y, lo que es más importante, en cuántas formas puede salir mal. La solución ingenua es simplemente almacenar el nombre de usuario/correo electrónico y la contraseña de un usuario directamente en la base de datos, y luego comparar la contraseña enviada con la almacenada. Si bien esto es esencialmente lo que debería suceder durante la autenticación, en realidad hay mucho más.

A lo largo de este artículo, explicaré cómo debe realizar la autenticación y las razones detrás de ella. Puede volverse bastante loco con algunas de estas cosas (como los requisitos de contraseña que impone), por lo que a lo largo del artículo intentaré ceñirme a las técnicas que son:

  1. Razonable de implementar
  2. Razonable para que el usuario siga
  3. Muy seguro

Requisitos de contraseña {#requisitos de contraseña}

Como todas las otras técnicas que describiré aquí, hacer cumplir los requisitos de contraseña es una necesidad absoluta. Básicamente, estás protegiendo al usuario de sí mismo. Basta con mirar este análisis de volcados de bases de datos recientes con contraseñas de texto sin formato, muchas de ellas son tan simples como 123456 o incluso contraseña. Depende de usted asegurarse de que sus usuarios no se vuelvan perezosos con sus cuentas.

Los siguientes requisitos son un buen lugar para comenzar:

  • Mínimo 8 caracteres
  • Debe contener al menos 1 letra minúscula (a-z)
  • Debe contener al menos 1 letra mayúscula (A-Z)
  • Debe contener al menos 1 número (0-9)
  • Debe contener al menos 1 carácter especial (!, @, #, $, %, ^, etc.)

Por alguna razón, algunos sitios web han puesto una longitud máxima en las contraseñas, lo que deberías absolutamente no deberías hacer. Deberá codificar las contraseñas antes de que se guarden en la base de datos, por lo que todas tendrán la misma longitud de caracteres al final de todos modos, independientemente de si el usuario ingresa una contraseña de 8 o 150 caracteres. No hagas esto.

Si le preocupa que alguien abuse de esto (como enviarle una cadena de megabytes), asegúrese de que el límite sea realmente alto, como unos pocos cientos de caracteres. Unos pocos cientos de caracteres pueden parecer un poco ridículos, pero algunas personas crean contraseñas largas/complicadas y luego simplemente copian y pegan durante la autenticación, así que no esperes que no suceda.

Además, no restrinja qué tipos de caracteres especiales se pueden usar. Cualquier cosa que el usuario pueda ingresar es un juego justo, incluso los espacios.

Para mayor seguridad:
Obligar al usuario a cambiar periódicamente su contraseña después de un período de tiempo, como una vez al año. También puede hacer lo que hace Google y no permitir que el usuario reutilice ninguna de sus contraseñas anteriores.

Transferencia de datos

El primer paso importante de todo el proceso es enviar los datos del usuario (ya sea desde una aplicación o una página web) a su servidor. Aunque esto debería ser evidente, siempre debe usar HTTPS aquí.

Solía ​​ser costoso e inconveniente comprar y administrar sus certificados SSL, pero ahora hay algunos servicios que cuestan solo $16.00 por año por un certificado Validado por Dominio. Y tome Compañero SSL por ejemplo: le permiten comprar e instalar sus certificados directamente desde la línea de comandos de su servidor. Y si elige hacerlo, también puede hacer que sus certificados se renueven automáticamente y se instalen automáticamente. No quedan muchas excusas para no tener HTTPS si tiene una aplicación web basada en el usuario.

Nota al margen: No, SSLMate no me paga, acabo de tener buenas experiencias con su producto.

Además de usar HTTPS, solo debe usar el método POST para enviar datos confidenciales y no en cadenas de consulta de URL a través de GET. Solo piénsalo, ¿qué pasa si iniciaste sesión en un sitio web usando una URL como:

1
https://wikihtp.com/login?username=scott&password=myPassword

Si bien esto técnicamente podría funcionar, no sería muy inteligente. Si envía datos confidenciales utilizando este método, la URL se guardará en el historial de su navegador y lo más probable es que también se guarde en los registros del servidor. Los datos POST generalmente no se registran a menos que se configuren explícitamente para hacerlo, lo cual es raro, por lo que es mucho más seguro que los métodos GET. Los registros suelen ser mucho más fáciles de acceder (y se manejan con más descuido) que los datos en una base de datos, por lo que sería mejor mantener todos los datos de sus usuarios fuera de allí.

Para mayor seguridad:
Use Seguridad de transporte estricta HTTP, que es una política de seguridad web estandarizada que permite a los servidores web decirle al navegador del cliente que solo interactúe con ese sitio web a través de HTTPS y no HTTP. Esto no solo garantizará que sus usuarios obtengan los beneficios de una conexión HTTPS, sino que también protegerá los sitios web HTTPS de ataques de degradación.

Hashing de contraseñas

En primer lugar, nunca jamás jamás almacene contraseñas en texto sin formato. En serio, simplemente no lo hagas. Es tan increíblemente fácil codificar una contraseña que realmente no hay razón para no hacerlo. Incluso le mostraré el código de cómo hacerlo en algunos idiomas populares diferentes para que pueda comenzar.

Existen bastantes tipos de algoritmos hash (MD5, SHA-0, SHA-1, SHA-3, etc.), por lo que puede que no sea obvio cuál es el mejor para usar. cripta es ampliamente considerado como el mejor algoritmo hash (bueno, técnicamente es una [función de derivación clave](https://en.wikipedia. org/wiki/Key_derivation_function)), en gran parte porque es una función adaptativa. Lo que eso significa es que puede aumentar el recuento de iteraciones (la cantidad de rondas de expansión clave) para hacerlo más lento, lo que ayudará a que siga siendo resistente a los ataques de fuerza bruta.

Aquí está el código para codificar contraseñas con bcrypt en algunos idiomas populares:

JavaScript:

1
2
var bcrypt = require('bcryptjs');
var hash = bcrypt.hashSync('somePassword', bcrypt.genSaltSync(10));

python:

1
2
import bcrypt
hash = bcrypt.hashpw('somePassword', bcrypt.gensalt())

Rubí:

1
2
require 'bcrypt'
hash = BCrypt::Password.create('somePassword')

Mira, no es tan malo, ¿verdad?

Otra cosa a considerar es dónde debe ocurrir el hashing: ¿lado del cliente o del lado del servidor? Puede tener sentido para usted hacer un hash en el lado del cliente y luego enviar el hash al servidor, lo que evitaría que se envíe la contraseña de texto sin formato. Si bien intuitivamente esto tiene sentido, considere un atacante que husmea en los datos que se envían al servidor. Si reciben la contraseña hash que se envía, entonces todo lo que tienen que hacer es enviar ese mismo hash al servidor para autenticarse, por lo que ni siquiera necesitan saber la contraseña.

Sin embargo, el principal problema radica en el escenario en el que un atacante obtiene acceso a una base de datos de estos hashes del lado del cliente, lo que les daría acceso instantáneo a todas las cuentas, ya que no se produjo ningún hash en el servidor. Así que supongo que podrías decir que puedes hacer hash del lado del cliente, pero aún así tendrías que hacer hash del lado del servidor también.

Para mayor seguridad:
Un método que puede usar para mayor seguridad (aunque algunas personas argumentarán que no es tan útil) es hacer que sea aún más difícil para los atacantes romper un hash usando combinaciones de hash no convencionales como esta:

  • sha3(sha2(contraseña + sal))
  • sha3(sha1(contraseña) + md5(sal))

Esto se llama seguridad a través de la oscuridad. Si vas a hacerlo de esta manera, al menos asegúrate de estar usando un algoritmo hash fuerte. Al final, bcrypt probablemente siga siendo una mejor opción ya que es adaptativo, pero si no quieres usarlo ya que consume muchos recursos computacionales, entonces esto al menos te da una alternativa.

Hacer que tus hashes sean más fuertes

Crear un hash de la contraseña del usuario es el primer paso y un buen lugar para comenzar, pero para que sus hashes sean realmente seguros, querrá agregar [sal](https://en.wikipedia.org/wiki/Salt_ %28criptografía%29) al hash. Hash salt es solo una cadena aleatoria que se agrega a la cadena que desea codificar, lo que ayuda a evitar el cracking con tablas de búsqueda o [mesas de arcoiris] (https://en.wikipedia.org/wiki/Rainbow_table). En código, podría verse así:

1
2
salt = genSalt()
hash = hashAlg('somePassword' + salt)

La teoría detrás del uso de sal es que si dos usuarios tienen la misma contraseña (sin sal), también tendrán el mismo hash. Entonces, si un atacante tiene una tabla de búsqueda o ya ha descifrado una contraseña una vez, entonces no hay más trabajo por hacer para todas las demás contraseñas coincidentes. El atacante simplemente usaría la tabla de búsqueda. Sin embargo, si se usa salt, incluso las contraseñas coincidentes tendrán hashes únicos que son diferentes entre sí.

Esto es cierto solo si el salt es único e impredecible para cada usuario, así que asegúrese de utilizar un generador de números pseudoaleatorios criptográficamente seguro (CSPRNG) para generar el salt. os.urandom en Python y java.security.SecureRandom en Java son CSPRNG.

Esto es lo que parece saltear un hash de contraseña:

1
2
hash1 = sha3('somePassword' + 'N7v4bL1YZU4xJw5A')   // b7854b0f3c9422b4ee4f6e590d8c95897c53aacce9ab6e0c0ab05f0a4d986407
hash2 = sha3('somePassword' + 'z8WnrKRcu9D3BeOK')   // df56f4a8876f53900575b784a594bbce7e2ab3e913ba146f13c6817c295e5f09

Conclusión

Hay bastantes otras cosas que puede hacer para ayudar a mantener sus datos y usuarios seguros (autenticación de dos factores, OAuth, etc.), pero muchas de ellas están fuera del alcance de este artículo, así que las dejaré para otro momento.

Existen bastantes recursos y bibliotecas de códigos para ayudarlo con algunas de las cosas que he cubierto aquí, así que utilícelos para su ventaja. No hay razón para reinventar la rueda, aunque todavía debe al menos saber cómo y por qué funciona.

¿Qué otros consejos tienes para asegurar tu proceso de autenticación? ¡Cuéntanos en los comentarios!