Al insertar hashes de contraseña en el cliente, los estaría exponiendo a ataques de fuerza bruta sin conexión. Eso puede ser completamente aceptable si las contraseñas son lo suficientemente fuertes. Pero esa es una suposición peligrosa.
Puede pedir a cada usuario que confirme que desea que se publique su propio hash (después de explicarles el riesgo). Pero hay muchos usuarios que con gusto dirían que entienden el riesgo y que han elegido una contraseña lo suficientemente fuerte, aunque lo que realmente eligieron fue password12345!!!
En este punto, tendrá que evaluar qué tan importante es esta característica en comparación con el riesgo de permitir que los usuarios se disparen en el pie.
Sin embargo, hay otro inconveniente en su diseño que es el de autenticar las actualizaciones realizadas fuera de línea. Confiar en el cliente para validar al usuario no es lo suficientemente bueno.
Almacenar la contraseña del usuario junto con la actualización y enviarla al servidor una vez en línea resolvería el problema de realizar solo la validación del lado del cliente. Pero introducirías tres nuevos problemas:
- No detectaría credenciales mal escritas al ingresar actualizaciones. Y más adelante, cuando el servidor verifique esas credenciales, es posible que el usuario que las escribió en primer lugar no pueda solucionar el problema.
- La actualización sería maleable. Sería trivial modificar la actualización mientras está almacenada en el dispositivo cliente para que realice una actualización diferente una vez que llegue al servidor.
- Estaría almacenando contraseñas en texto sin formato en el dispositivo, lo cual es un gran no-no hay seguridad.
No veo ninguna forma de evitar la fuerza bruta sin conexión de las contraseñas y al mismo tiempo permite que la contraseña se valide en el momento en que se ingresa una actualización, y esa validación es necesaria por razones de uso.
Por lo tanto, el enfoque que tomaría es crear un par de claves públicas basadas en la contraseña del usuario y un salt, y usar la clave privada correspondiente para firmar la actualización. Eso significa que la actualización ya no es maleable, y la contraseña no necesita ser almacenada y transmitida, sino que simplemente almacena y transmite la firma.
Por supuesto, el cliente todavía necesita saber sal y el nombre de usuario para generar una transacción firmada correctamente. Y tendría que conocer la clave pública o un hash para verificarla en primer lugar, lo que permite ataques de fuerza bruta sin conexión.
Puede truncar el lado del cliente verificado por hash a un solo byte, lo que hace que los ataques de fuerza bruta sin conexión sean menos factibles y aún tiene un 99% de posibilidades de detectar contraseñas incorrectas en el lado del cliente.
En el lado del servidor, todavía debe verificar la clave pública transmitida con un hash salado para asegurarse de que solo reciba transacciones válidas.
Obviamente, cualquier diseño para la aplicación que está diseñando deja mucho margen para errores, por lo que contar con el diseño y el código final revisados por múltiples profesionales de la seguridad es una necesidad para garantizar que el resultado final sea seguro.