Сделаем некоторые заметки к предыдущим ответам.
Во-первых, вероятно, не лучшая идея использовать на стороне клиента хэш-алгоритмы. Если ваш пароль соленый на стороне сервера, вы не сможете сравнивать хеши (по крайней мере, если вы не храните клиентский хеш в базе данных в одном из уровней хеширования из пароля, который совпадает или хуже). И вы не хотите реализовывать алгоритм хеширования, используемый базой данных на стороне клиента, это было бы глупо.
Во-вторых, обмен криптографическими ключами тоже не идеален. MITM теоретически может (учитывая, что у него установлен корневой сертификат на клиенте) изменить криптографические ключи и изменить его собственными ключами:
Исходное соединение (без учета tls) от теоретического сервера, который обменивается ключами:
Открытые ключи запроса клиента> сервер содержит закрытые ключи, генерирует открытые ключи для клиента> сервер отправляет открытые ключи клиенту
Теперь в теоретическом треке MITM:
Открытые ключи клиентского запроса> MITM генерирует поддельные закрытые ключи > Сервер хранит закрытые ключи, генерирует открытые ключи для клиента> MITM получает открытые ключи от исходного сервера, теперь мы можем отправлять наши поддельные открытые ключи клиенту, и всякий раз, когда от клиента поступает запрос, мы будем расшифровывать данные клиента с помощью поддельных ключей, изменять полезную нагрузку (или читать ее) и шифровать исходными открытыми ключами > MITM отправляет клиенту поддельные открытые ключи.
В этом смысл наличия доверенного сертификата CA в TLS, и именно так вы получаете сообщение от браузера, предупреждающее, если сертификат недействителен.
В ответ на OP: по моему скромному мнению, вы не можете этого сделать, потому что рано или поздно кто-то захочет атаковать пользователя из вашей службы и попытается нарушить ваш протокол.
Однако вы можете реализовать 2FA, чтобы люди никогда не пытались войти в систему с тем же паролем. Тем не менее, помните о повторных атаках.
Я не очень разбираюсь в криптографии, поправьте меня, если я ошибаюсь.