Implementar la autenticación de usuario de la manera correcta

    Introducción

    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 fallar. 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 aplica), por lo que a lo largo del artículo intentaré ceñirme a las técnicas que son:

    • Razonable para su implementación
    • Razonable para que el usuario siga
    • Muy seguro

    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. Solo mira este análisis de los volcados de bases de datos recientes con contraseñas de texto sin formato, muchos de ellos son tan simples como 123456 o incluso password. Depende de usted asegurarse de que sus usuarios no se vuelvan perezosos con sus cuentas.

    Los siguientes requisitos son un buen punto de partida:

    • Mínimo 8 caracteres
    • Debe contener al menos 1 letra minúscula (az)
    • Debe contener al menos 1 letra mayúscula (AZ)
    • 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, que debería absolutamente no debería hacer. Estará procesando las contraseñas antes de que se guarden en la base de datos, por lo que todas tendrán la misma longitud de carácter 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 cientos de caracteres pueden parecer un poco ridículos, pero algunas personas crean contraseñas locas, largas y complicadas y luego simplemente las copian y pegan durante la autenticación, así que no espere que no ocurra.

    Además, no restrinja los tipos de caracteres especiales que se pueden usar. Todo lo que el usuario pueda ingresar es un juego limpio, incluso 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 antiguas.

    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 debería utilizar 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 de dominio validado. Y tomar SSLMate por ejemplo, le permiten comprar e instalar sus certificados directamente desde la línea de comandos de su servidor. Y si decide hacerlo, también puede hacer que sus certificados se renueven y se instalen automáticamente. No quedan muchas excusas para no tener HTTPS si tiene una aplicación web basada en usuarios.

    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énselo, ¿qué pasa si inicia sesión en un sitio web usando una URL como:

    https://Pharos.sh.com/login?username=scott&password=myPassword
    

    Si bien esto técnicamente podría funcionar, no sería muy inteligente. Si envía datos confidenciales mediante este método, la URL se guardará en el historial de su navegador y probablemente también se guardará en los registros del servidor. Los datos POST generalmente no se registran a menos que estén configurados explícitamente, lo cual es raro, por lo que es mucho más seguro que los métodos GET. Por lo general, es mucho más fácil acceder a los registros (y se manejan de manera más descuidada) que a los datos de una base de datos, por lo que es mejor que no guarde todos los datos de sus usuarios.

    Para mayor seguridad:

    Utilizar 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 de HTTP. Esto no solo garantizará que sus usuarios obtengan los beneficios de una conexión HTTPS, sino que también protege los sitios web HTTPS de ataques de degradación.

    Hash de contraseñas

    En primer lugar, nunca jamás almacene las 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 te mostraré el código de cómo hacerlo en algunos lenguajes populares diferentes para que comiences.

    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. bcrypt es ampliamente considerado como el mejor algoritmo de hash (bueno, técnicamente es un función de derivación clave), en gran parte porque es una función adaptativa. Lo que eso significa es que puede aumentar el recuento de iteraciones (el número de rondas de expansión clave) para hacerlo más lento, lo que lo ayudará a seguir siendo resistente a los ataques de fuerza bruta.

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

    JavaScript:

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

    Python:

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

    Rubí:

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

    Ves, no es tan malo, ¿verdad?

    Otra cosa a considerar es dónde debería ocurrir el hash: ¿del lado del cliente o del lado del servidor? Puede que tenga 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. Aunque intuitivamente esto tiene sentido, considere a un atacante que fisgonea en los datos que se envían al servidor. Si reciben la contraseña con hash, 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 hash del lado del cliente, lo que le 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ía decir que puede aplicar hash en el lado del cliente, pero también necesitaría hacerlo en el lado del servidor.

    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 utilizando combinaciones de hash no convencionales como esta:

    • sha3(sha2(password + salt))
    • sha3(sha1(password) + md5(salt))

    A esto se le llama seguridad por oscuridad. Si lo va a hacer de esta manera, al menos asegúrese de que está utilizando un algoritmo hash sólido. Al final, es probable que bcrypt siga siendo una mejor opción ya que es adaptable, pero si no desea usarlo ya que consume muchos recursos computacionales, al menos esto le brinda una alternativa.

    Haciendo sus hashes más fuertes

    El 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 al picadillo. Hash salt es solo una cadena aleatoria agregada a la cadena que desea hash, lo que ayuda a evitar que se rompan las tablas de búsqueda o mesas arcoiris. En código, podría verse así:

    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, 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 hash únicos que son diferentes entre sí.

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

    Así es como se ve sal un hash de contraseña:

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

    Conclusión

    Hay algunas 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 dejo para otro hora.

    Hay bastantes recursos y bibliotecas de código para ayudarlo con algunas de las cosas que he cubierto aquí, así que úselas para su ventaja. No hay razón para reinventar la rueda, aunque al menos deberías saber cómo y por qué funciona.

    ¿Qué otros consejos tiene para asegurar su proceso de autenticación? ¡Háznoslo saber en los comentarios!

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *