BCrypt del lado del cliente, almacene sal y hash por separado en la base de datos MySQL [duplicado]

9

Estoy desarrollando una aplicación de Android con una base de datos MySQL para almacenar las credenciales de inicio de sesión del usuario. Estoy usando jBCrypt para las contraseñas de hashing antes de almacenarlas en la base de datos. En el registro, la contraseña se incluye en el lado del cliente de la siguiente manera:

String salt = BCrypt.gensalt();
String hash = BCrypt.hashpw("password", salt).split("\$")[3];
salt = salt.split("\$")[3];
hash = hash.substring(salt.length(), hash.length());

En este caso, BCrypt.hashpw() me dará el hash

  

$2a$10$rrll.6qqZFLPe8.usJj.je0MayttjWiUuw/x3ubsHCivFsPIKsPgq

Luego elimino los parámetros ( $2a$10$ ) y almaceno los primeros 22 caracteres como sal y los últimos 31 como hash en la base de datos:

------------------------------------------------------------------------
|  uid  |          salt            |               hash                |
------------------------------------------------------------------------
|   1   |  rrll.6qqZFLPe8.usJj.je  |  0MayttjWiUuw/x3ubsHCivFsPIKsPgq  |
------------------------------------------------------------------------

Ahora, cuando un cliente desea iniciar sesión, ingresa su nombre de usuario y contraseña y solo se devuelve el salt de la base de datos. El cliente calcula su hash llamando a BCrypt.hashpw() con su salt:

String salt = "$2a$10$" + returnedSalt;
String hash = BCrypt.hashpw(“password”, salt).split("\$")[3];
hash = hash.substring(salt.length(), hash.length());

dandome:

  

hash = "0MayttjWiUuw/x3ubsHCivFsPIKsPgq"

que es igual al hash almacenado en la base de datos. El cliente luego envía el nombre de usuario y el hash calculado al servidor. Si coinciden, el usuario inicia sesión.

Sé que puedo simplificar este proceso obteniendo directamente todo el hash de BCrypt y comparándolo con la contraseña dada con

if (BCrypt.checkpw(“password”, bCryptHash))
  // match

pero se siente mal al enviar la contraseña completa al usuario para que realice la comprobación.

Entiendo que es preferible hash las contraseñas del lado del servidor, pero ¿hay algún problema con esta solución? ¿Me estoy perdiendo algo?

Diga que tengo una conexión HTTP sin cifrar entre el teléfono y el servidor. ¿Sería esta una solución segura?

    
pregunta yberg 08.03.2016 - 18:44
fuente

3 respuestas

36

El cliente es el atacante. Camine por su oficina mientras canta esa frase 144 veces; Asegúrese de puntuar su dicción con un tambor pequeño. De esa manera, lo recordarás.

En su servidor, está enviando código Java para ejecutarse en el cliente. El cliente honesto ejecutará su código. Nadie obliga al atacante a hacerlo también; y utiliza la autenticación del cliente precisamente porque teme que el cliente pueda ser otra persona que intente hacerse pasar por el usuario normal. Desde el punto de vista de su servidor, solo ve los bytes que envía al cliente y los bytes que regresan. No puede asegurarse de que estos bytes se hayan computado con su código. El atacante, siendo un sinvergüenza malvado, puede imaginar perfectamente que no ejecuta su código y en su lugar envía la respuesta que su servidor espera.

Considere ahora un esquema de autenticación de contraseña simple donde simplemente almacena las contraseñas de los usuarios en su base de datos. Para autenticarse, pídale al usuario que envíe la contraseña y luego compárela con lo que almacenó. Esto es muy simple. Esto también es muy malo: se llama contraseñas de texto simple . El problema es que cualquier simple vistazo de solo lectura en la base de datos (ya sea un ataque de inyección SQL, una cinta de respaldo robada, un disco duro antiguo recuperado de un contenedor de basura ...) le dará al atacante todas las contraseñas. Para indicar las cosas claramente, el error aquí es almacenar en la base de datos exactamente los valores que, cuando se envían desde el cliente, otorgan acceso.

¿Y tu esquema propuesto? Exactamente lo mismo. Almacena en la base de datos el valor de hash "tal cual". Y cuando el cliente envía ese valor exacto, se concede el acceso. Por supuesto, el cliente honesto enviará el valor con una contraseña. Pero, seamos sinceros: muchos atacantes no son personas honestas.

Ahora hay valor en hacer parte del hashing en el lado del cliente. De hecho, el buen hashing de contraseña es una carrera de armamentos, en la que el hashing se hace caro a propósito. Descargar algo del trabajo en los clientes puede ser algo bueno. No funciona tan bien cuando los clientes son débiles, por ejemplo, Los teléfonos inteligentes con Java o, lo que es peor, Javascript (que es una cosa completamente diferente, a pesar de la similitud del nombre).

En ese caso, necesitaría ejecutar bcrypt en el cliente, y almacenar en el servidor no la salida de bcrypt, sino el hash de la salida de bcrypt con alguna función hash razonable (una rápida como SHA-256 estaría bien). El procesamiento de una contraseña P sería entonces un cifrado en el cliente, luego un SHA-256 del resultado, calculado en el servidor. Esto impulsará la mayor parte del gasto de CPU en el cliente y será tan seguro como un bcrypt sin formato para lo que está destinado a hacer (ver más abajo).

Entiendo que desea "cifrar" las contraseñas (¡el hashing no es cifrado!) porque desea usar HTTP simple. No te gusta HTTPS por la misma razón que todos los demás, que es el temido certificado SSL. Pagar 20 dólares al año por un certificado SSL sería similar a que le retiren la piel con un pelador de papas con jugo de limón.

Lamentablemente, no hay escape del pelador. Como han dicho otros, incluso si tiene un mecanismo de autenticación sólido, HTTP sin procesar aún está desprotegido y un atacante activo puede simplemente esperar a que un usuario se autentique y secuestrar la conexión desde ese punto. Un ejemplo muy común de "atacante activo" son personas que simplemente ejecutan un punto de acceso WiFi falso, es decir, un punto de acceso WiFi completamente real, pero también tienen la opción de secuestrar conexiones en cualquier momento. Este es un tipo de modelo de ataque que no se puede contrarrestar sin un protocolo criptográfico completo que se extienda sobre todos los datos, no solo un método de autenticación inicial. Acerca del tipo más simple de este tipo de protocolo es SSL / TLS. Cualquier protocolo que proporcione las mismas garantías, que absolutamente necesita, también será tan complejo como SSL / TLS, y mucho más difícil de implementar porque, al contrario de SSL / TLS, aún no está implementado en el software cliente.

SSL está ahí, solo úsalo. En cuanto al limón, chúpalo.

(Si el costo financiero es una barrera, hay alternativas gratuitas, como Let's Encrypt y pki.io . Si se ajustan a su factura aún no se ha visto, pero vale la pena considerarlas si realmente tiene poco efectivo.)

    
respondido por el Tom Leek 08.03.2016 - 21:59
fuente
9

Si no está utilizando una conexión segura (https), su esquema tiene fallas de varias maneras:

  • La contraseña con hash es todo lo que se necesita para iniciar sesión: eres vulnerable a un ataque de repetición;
  • Como la conexión no está encriptada, el javascript que envía al cliente puede ser manipulado por el atacante (el atacante podría dejar que el cliente le envíe la contraseña).

Si desea un inicio de sesión seguro, debe poder garantizar que el código del lado del cliente es correcto y que no hay nada de valor para ser detectado en el cable.

En casi todas las circunstancias, la mejor solución para esto es utilizar una conexión segura SSL / TLS. Debería enviar la contraseña sin hash a través de esta conexión segura y hacer el hashing en el lado del servidor.

Además, al eliminar la parte $2a$10$ , se evita aumentar el recuento de iteraciones en cualquier lugar en el futuro: como no almacena el recuento de iteraciones, no tiene ningún método para identificar hashes antiguos de los nuevos después de que decida aumentar el número de iteraciones.

( Si puede garantizar el código del cliente, podría utilizar teóricamente SRP , pero hacerlo bien es un asunto complejo y es probable que falle).

    
respondido por el Jacco 08.03.2016 - 20:37
fuente
2

Si está enviando algo a través de una conexión HTTP sin cifrar, suponga que se está viendo y que se puede comprometer con MITM.

Si alguien fuera a MITM, podría suministrar su propia sal y responder con su propio hash. Por lo tanto, cualquier atacante podría falsificar completamente su proceso de autenticación.

Debería estar codificando el lado del servidor de contraseñas y utilizando una conexión segura.

    
respondido por el d1str0 08.03.2016 - 18:51
fuente

Lea otras preguntas en las etiquetas