У Steam довольно любопытный способ логина

Автор оригинала: Owlspace
  • Перевод
image

Как передать пароль по Интернету? Обычно приобретается сертификат SSL, а TLS выполняет задачу безопасной перемещения пароля от клиента к серверу. Разумеется, всё не так сухо, как пытаюсь представить я, но в целом это так и подобный подход прошёл проверку временем. Однако так было не всегда, и один невероятно популярный онлайн-магазин предпочёл добавить к этому процессу что-то своё. В этой статье я расскажу об уникальном способе входа в систему пользователей Steam и исследую глубокую кроличью нору удивительных подробностей его реализации.

Выявляем очевидное


Я нашёл на StackOverflow датированный 2013 годом вопрос о том, как безопасно передавать пароль по HTTP. Ответы оказались достаточно единодушными: надо получить сертификат SSL. Проведите эксперимент: настройте любимый прокси перехвата трафика, зайдите в сервис, которым вы часто пользуетесь, выполните вход со своим аккаунтом (а лучше каким-нибудь одноразовым) и изучите результаты. С большой вероятностью вы увидите, что имя пользователя и пароль передаются в открытом виде в теле запроса HTTP. Единственная причина того, что это работает, заключается в том, что ваше соединение с сервером зашифровано при помощи TLS.


Странно думать, что когда-то это было проблемой

Однако в начале 2010-х, не говоря уже о более давнем времени, Интернет был иным. Теперь у нас есть сервисы наподобие Let’s Encrypt, выдающие бесплатные сертификаты SSL с трёхмесячным сроком действия и возможностью автоматического обновления. Тогда почти не было иных вариантов, кроме приобретения сертификата SSL за деньги, но обычно при этом можно было получить более длительные сроки действия и поддержку. Конечно, вы можете сказать, что за безопасность и приватность пользователей стоит платить, но это не мешает появляться вопросам наподобие приведённого выше.

Итак, мы пришли к понимаю, что TLS важен, а теперь давайте его заменим. Представим, что мы не можем передавать пароли через HTTPS и нам каким-то образом нужно реализовать это на чистом HTTP, обеспечив при этом какой-то уровень безопасности. Существует стандартизованный и широко применяемый заголовок Authorization. Однако, в сочетании со схемой аутентификации HTTP «Basic» при использовании с чистым HTTP он не обеспечивает никакой защиты.

Есть проверенные и протестированные алгоритмы запроса-ответа, наиболее примечательным из которых является SRP, предназначенный для парольной аутентификации без передачи пароля, но их, вероятно, придётся реализовывать самостоятельно, и даже небольшой недосмотр может привести к серьёзному ущербу. Также можно поручить аутентификацию внешнему сервису. Схема «вход при помощи сервиса XYZ» широко распространена, но она связана с определёнными последствиями. С учётом всего этого, можно сказать, что передача секретов по не предназначенному для безопасности соединению — это нетривиальная задача.

Поэтому когда мы с другом решили изучить Steam в поисках следов информации, позволяющей идентифицировать людей, меня удивило то, что для обеспечения безопасности страница логина Steam использует только TLS.

Крипто-вишенка на торте


Снова запустим любимый прокси перехвата трафика и перейдём на страницу логина Steam. Введём имя пользователя и пароль, после чего нас попросят (по крайней мере, должны) ввести одноразовый токен, сгенерированный выбранным вами способом двухфакторной аутентификации. На этом можно остановиться, потому что магия, которую я хочу продемонстрировать, уже произошла. Вы заметите, что при нажатии кнопки логина запускается запрос странной конечной точки: /login/getrsakey, за которой следует /login/dologin.


Последовательность всех соответствующих ресурсов и запросов

Если изучить запрос к /login/getrsakey, то мы обнаружим ответ в формате JSON, содержащий поля с названиями, хорошо знакомыми всем, кто хотя бы немного знает о криптографии с открытым ключом. Нам передаётся открытый ключ RSA, хотя конкретные значения могут выглядеть немного странно. Очевидно, что publickey_mod и publickey_exp определяют используемые в шифровании модуль и экспонента, однако первый задаётся в шестнадцатеричном виде, а вторая — в двоичном (я вернусь к этому позже). Есть также метка времени, начальную точку которой можно определить не сразу. С назначением token_gid я пока не разобрался.

{
    "success":true,
    "publickey_mod":"c85ba44d5a3608561cb289795ac93b34d4b9b4326f9c09d1d19a9923e2d136b8...",
    "publickey_exp":"010001",
    "timestamp":"1260462250000",
    "token_gid":"2701e0b0a4be3635"
}

Страница логина при загрузке подгружает скрипты. В совершенно необфусцированном login.js содержится основной обработчик логина, поэтому любой может просто проанализировать его и разобраться, что он делает. Кроме того, сайт загружает дополнительные зависимости, в частности, jsbn.js и rsa.js.

Поиск по имени, указанному в первой строке jsbn.js, позволил определить, что эти два скрипта написаны выпускником MIT и Стэнфорда Томом Ву, любящим проектирование ПО и компьютерную криптографию. Он выпустил jsbn.js и rsa.js как реализацию на чистом JavaScript целых чисел с произвольной точностью и шифрования/дешифрования RSA. Также мы можем выяснить, что эти библиотеки в последний раз обновлялись в 2005 и 2013 годах, но к этому я вернусь позже. Пока просто запомним это.

Спускаемся в кроличью нору


Итак, у нас есть все нужные ресурсы, и мы можем углубиться в login.js. Его код довольно хаотичен, со множеством обратных вызовов и вызовов прокси-функций, но самые интересные части можно найти довольно быстро. По сути, скрипт можно свести к паре шагов. Каждый из шагов предполагает, что на предыдущем всё прошло правильно.

  1. Пользователь вводит своё имя пользователя и пароль, а затем нажимает кнопку логина.
  2. Вызывается DoLogin, который проверяет, правильно ли заполнена маска логина и выполняет запрос к /login/getrsakey.
  3. Вызывается OnRSAKeyResponse. Он проверяет, правильно ли сформирован запрос.
  4. Вызывается GetAuthCode. Он выполняет какой-то платформно-зависимый код в случае, если для аккаунта пользователя включены способы двухфакторной авторизации.
  5. Вызывается OnAuthCodeResponse. Здесь пароль шифруется RSA, подготавливается и выполняется запрос к /login/dologin.
  6. Вызывается OnLoginResponse. Пользователь выполняет вход и перенаправляется в магазин Steam.

Код в OnAuthCodeResponse показывает, почему запрашиваемый открытый ключ форматирован именно таким образом. Начиная со строки 387 исходного файла модуль и экспонента ответа /login/getrsakey передаются в неизменном виде библиотеке RSA. Затем пароль пользователя шифруется переданным открытым ключом и добавляется в запрос к /login/dologin на следующем этапе логина.

var pubKey = RSA.getPublicKey(results.publickey_mod, results.publickey_exp);
var username = this.m_strUsernameCanonical;
var password = form.elements['password'].value;
password = password.replace(/[^\x00-\x7F]/g, ''); // remove non-standard-ASCII characters
var encryptedPassword = RSA.encrypt(password, pubKey);

Я скопировал файлы исходников на локальную машину, чтобы подробнее изучить библиотеку RSA. Модуль и экспонента передаются функции RSAPublicKey, которая ведёт себя как конструктор в «доклассовой» эпохе JavaScript. RSAPublicKey просто оборачивает значения в экземпляры BigInteger предоставленные скриптом jsbn.js. К моему удивлению, экспонента представлена не в двоичном виде, а как и модуль, в шестнадцатеричном. (Кроме того, выяснилось, что 0x010001 — это очень популярная экспонента шифрования в реализациях RSA.) То есть теперь понятно, что шифрование паролей основано на 2048-битном RSA с экспонентой шифрования, равной 65537.

let r = RSA.getPublicKey("c85ba44d5a360856..." /* insert your own long modulus here */, "010001");
console.log(r.encryptionExponent.toString()); // => "65537"
console.log(r.modulus.bitLength()); // => 2048

Перейдём к полю timestamp. Ответ /login/getrsakey содержит заголовок Expires. Он ссылается на дату в прошлом, то есть ответ совершенно не должен каким-то образом кэшироваться или сохраняться. Если следить за /login/getrsakey дольше, то мы заметим, что открытый ключ постоянно часто меняется, как и метка времени. Это означает, что есть ограниченное окно времени, в течение которого конкретный открытый ключ RSA, выданный Steam, может использоваться для аутентификации.

Это становится ещё очевиднее при изучении последующего запроса к /login/dologin. Среди всего остального он содержит имя пользователя, зашифрованный пароль, а также метку времени выданного открытого ключа RSA. Попытка выполнить логин при изменении метки времени, как и ожидалось, оканчивается неудачей. Но важнее то, что невозможно повторно использовать старый открытый ключ даже при правильном шифровании пароля.

Я сделал ещё один шаг и написал простой скрипт на Python для сбора открытых ключей одноразового аккаунта на протяжении трёх дней. При помощи cronjob я запускал его каждые пять минут. Я хотел проверить, как часто меняется открытый ключ Steam и если удастся, понять, как ведёт себя поле timestamp.


Целая куча открытых ключей

Я выяснил, что открытый ключ меняется через каждые 12 попыток ввода, то есть можно логично предположить, что они заменяются каждый час. Экспонент шифрования остаётся тем же, здесь ничего неожиданного. Однако более интригующим оказалось вышеупомянутое поле timestamp. Для каждых 12 открытых ключей значение timestamp увеличивается на определённую величину, а именно на 3600000000. Кроме того, как видно на изображении выше, это число спустя определённое время зацикливается. Предупреждаю, дальнейшие рассуждения полны неподтверждённых догадок.


Поле timestamp зацикливается

Я выяснил, что 3600000000 микросекунд равно одному часу, поэтому предположил, что значение поля timestamp на самом деле задаётся в микросекундах. Однако я уже говорил, что значение метки времени с каждым новым открытым ключом не увеличивается ровно на час. На основании собранных данных я заметил, что разница между двумя последовательными метками времени равна одному часу плюс 1-2,6 секунд, и большинство из них имеет порядок 1,05-1,25 секунд. Но в таком случае возникает ещё одна интересная возможность.

Предположим, что новый открытый ключ генерируется каждый час плюс одна секунда. Если выполнять запрос к конечной точке ровно каждые пять минут (пока полностью игнорируя сетевую задержку), то есть вероятность, что я встречу один и тот же открытый ключ не 12, а 13 раз подряд. Это должно произойти, когда запрос совпадает по времени с генерацией нового открытого ключа. К счастью, поскольку это время порядка секунды, допуск на ошибку не так уж мал.


Разными цветами показаны уникальные открытые ключи (без соблюдения масштаба!)

Изучив мой собственный набор открытых ключей, я выяснил, что не сталкивался ни с одним таким пограничным случаем. Возможно, мне просто не повезло, или дело в том, что я рассуждал гипотетически, ожидая какого-нибудь откровения. Кроме того, при увеличении колебаний значений меток времени становится сложнее прогнозировать конкретный момент возможности наблюдения пограничного случая — если, конечно, такая ситуация вообще возникает.

Но помните, эта разница в час и одну-две секунды находится между двумя уникальными открытыми ключами. Вернёмся к допущению о том, что новый открытый ключ создаётся через каждый час и секунду. Тогда после 3600 публичных ключей все эти дополнительные секунды суммируются в полный час, что приведёт к описанному в предыдущем параграфе пограничному случаю. Если разница времени происходит между полным часом на часах и меткой времени открытого ключа, то всё становится понятным и эти дополнительные секунды связаны с задержкой сети. Однако для собранных мной данных это не так, поэтому ситуация озадачивает, если не сказать больше.

Итак, подведём итог: если все мои допущения были верны, то поле timestamp и его временная разница между открытыми ключами невероятно загадочны. Нужна ли она для учёта високосных лет? Или для компенсации какой-то другой задержки? Может, это просто ошибка реализации, которую Valve оставила? Возможно, она выражена не в микросекундах, а в чём-то более произвольном? Может, она нужна просто для того, чтобы поломали голову любопытные нерды вроде меня? Я склоняюсь к последней версии.

Я понимаю, что пропустил странное зацикливание значения поля timestamp и даже не касался назначения поля token_gid. Думаю, что первое нужно из-за какого-то технического ограничения, а последнее — предположительно, какой-то способ защиты от CSRF (межсайтовой подделки запроса) или уникальный идентификатор. Это совершенно необоснованные догадки, потому что я и так уже выяснил из этого исследования больше, чем ожидал. Если хотите изучить вопрос самостоятельно и поделиться своими находками, буду рад, если вы свяжетесь со мной, или по электронной почте, или в Twitter.

Ещё один достойный упоминания аспект: при запросе конечной точки открытого ключа с разными именами пользователей получаешь разные ответы. Непонятно, или открытые ключи берутся из пула и каждому пользователю присваивается своё смещение метки времени, или они на самом деле генерируются на лету. Кроме того, в запросе к /login/getrsakey можно использовать любое произвольное имя пользователя. Оно не обязательно должно быть зарегистрировано в Steam. Можете использовать эту информацию по своему усмотрению.

Хорошо, но что это значит?


В процессе исследования этой темы во мне зародилась странная любовь к механизму логина Steam. Теперь я знаю, что поверх использования TLS (который и нужно применять) при выполнении входа пользователя Steam также использует 2048-битный RSA для шифрования паролей пользователей при помощи системы чередующихся открытых ключей, которая корректно признаёт недействительными старые ключи и для каждого пользователя действует по-своему. Все эти труды кажутся очень избыточными, потому что для защищённого логина пользователей вполне достаточно сертификата SSL.

Поэтому возникает вопрос: зачем заморачиваться созданием такой странно замысловатой системы поверх механизма, вполне достаточного самого по себе? У меня есть теория, но помните — это всего лишь догадка.

Помните даты выпуска библиотек BigInteger и RSA? Кроме того, страница логина по-прежнему использует в качестве источника jQuery версии 1.8.3, выпущенный в ноябре 2012 года. Всё это указывает на простой факт — механизм логина практически не менялся в течение почти десятка лет. А как я сказал в начале этого поста, в те времена Интернет был совершенно иным.


О! Я нашёл опечатку в changelog jQuery 1.8.3! Есть ли какая-нибудь программа вознаграждений за поиск грамматических ошибок?

В современном вебе развивается концепция «HTTPS повсюду», однако современная ситуация стала результатом долгого и мучительного процесса. Моя теория заключается в том, что в былые времена так Steam обеспечивала слой безопасности пользователей, которые случайно или намеренно не попали на SSL/TLS-версию сайта логина. Благодаря этому даже если третья сторона сможет проанализировать все данные, передаваемые между пользователем и серверами Steam, она, по крайней мере, не сможет узнать его пароль (без мощных вычислительных ресурсов).

Я попытался связаться с сотрудником Valve, который точно работает над магазином Steam. Я кратко изложил ему свой анализ и теорию. Я спросил его, может ли он подтвердить это, или, может быть, знает кого-нибудь, работавшего в компании во время создания этого способа логина. Разумеется, я знаю, что у сотрудников Valve есть дела поважнее, чем отвечать на несрочную и неделовую просьбу какого-то нерда. На момент написания статьи я по-прежнему ожидаю ответа, поэтому могу только предложить свою собственную обоснованную догадку. Как бы то ни было, это исследование оказалось очень, действительно очень интересным. Исследовано ещё не всё и на этом я не закончу.

Средняя зарплата в IT

120 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 3 620 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 31

    +13
    Вы упустили момент, что TLS защищает только трафик между клиентом и сервером, но при этом, он не сильно защищает от атаки Man-in-the-middle, если вы будете доверять атакующему.

    При этом, атакующий может быть и уровня государства (например — китай) или же ваш провайдер/работадатель, который требует установки определённых сертификатов для работы. Так же атакующий может установить на ваш локальный компьютер программу, которая будет проксировать все данные, и сделать чтобы браузер использовал именно эту программу как «прокси сервер».

    Т.е. основная идея разработчиков Steam: делаем так, чтобы пароль никогда не покидал браузер в чистом виде, чтобы атаки такого типа были невозможны.

      0
      Именно так, к сожалению некоторые разработчики считают TLS панацей от всего, и вообще не заботятся об механизмах аутентификации. Передают логин и пароль в открытом виде, пусть и по TLS. Только почему-то никто не думает что данные от пользователя могут быть завернуты на злобный прокси.
        +18

        Если злобный прокси может слушать канал — то он может его и менять. Ничего не мешает просто отдать клиенту немного другую версию скрипта шифрования, который чисто случайно помимо основной задачи ещё сбросит на нужный сервер нешифрованную версию пароля.


        Это именно та причина, почему если нет доверенного TLS, то последующие пляски с шифрованием полная фикция — через такой прокси троянов можно прямо в страницу напихать. Не говоря уже о том, что даже после вот этого очень шифрованного логина сессионная кука начинает летать открытым текстом

          –1

          Это не совсем делает затею бессмысленной, есть варианты атак от которых схема valve защищает. Например если ты такой гос-MITM и просто записываешь трафик, то у тебя не получится узнать пароль в ретроспективе. Тебе нужно провести подобны reverse-engineering для касмотной системы авторизации, подготовить атаку и ждать новой авторизации. Можно усложнить reverse engendering если использовать не известные RSA JS библиотеки, а что-то вроде WASM.


          Сессионная кука может быть привязана к IP, что может как-то ограничивать возможность ее использования.

            +6

            Это, скажем так, сводит всю затею к "злоумышленнику будет лень ковырять", т.е. security via obscurity.


            Думаю в случае с valve или любой другой крупной компании государев mitm уже заранее будет сконфигурирован как надо. А неуловимые Джо — они и в Африке такие. И никакой WASM не защищает от скрипта, который просто повесит глобальный хук на onkeydown. Ну и если у вас в канале сидит MITM — очевидно, он может предоставляться тем же IP, что и у клиента.

              –1
              «Цена атаки» в этой ситуации станет дороже «цены защиты».

              Т.е. да, можно, а что дальше? Что ты получишь? Только доступ к стиму для данного аккаунта? Зачем это нужно государству?
              Посмотреть в какие игры ты играешь? Они могут данные потребовать у стима напрямую.

              Я думаю что это больше защита от разного уровня script kiddie, и от автоматических логгеров трафика которые будут анализировать весь трафик на ключевые слова «username + password» и записывать данные.

                0

                Ну как же. Взломав и получив доступ к одному ресурсу, можно попытаться получить доступ к другим, более интересным для «определенных человечков».

                0
                "злоумышленнику будет лень ковырять", т.е. security via obscurity.

                Нет абсолютных способов защиты, есть лишь достаточные для той или иной ситуации.

                  0
                  Это, скажем так, сводит всю затею к "злоумышленнику будет лень ковырять", т.е. security via obscurity.

                  Obscurity это нормальный дополнительный уровень к security (в данном случае TLS). Государеву mitm нужно решать на что тратить ресурсы, obscurity может отпугнуть если это не особо важно, или алгоритм меняется раз в месяц.


                  И никакой WASM не защищает от скрипта, который просто повесит глобальный хук на onkeydown

                  Раньше на сайтах банков были OnScreenKeyboard. Я согласен, но это все равно уже активная атака, когда вы не можете узнать пароль просто по трафику. Это кстати может не только на стороне клиента, на стороне серверной архитектуры там где вы делаете TLS-termination и пакеты ходят в открытом виде внутри серверной сети, они остаются в закрытом виде не утекают дальше.


                  Ну и если у вас в канале сидит MITM — очевидно, он может предоставляться тем же IP, что и у клиента.

                  Верно, но лимит все равно есть: пароль так и не узнали и использовать аккаунт с другого IP невозможно.


                  Javascript-based криптография выглядит с точки зрения теории действительно не надежной. CrypoAPI насколько я знаю Chrome/Firefox отключают в скриптах когда к нему обращаются со странички полученной не по https.

            +3
            Но во всех этих случаях можно перехватывать запросы на rsa ключ и отдавать свой, после чего спокойно расшифровывать пароли.
            Так можно дойти до своей реализации TLS на js внутри браузера.
              +7

              Имея mitm, все эти "идеи" решаются тривиально. Достаточно отдать слегка модифицированный скрипт, который перед отправкой всей этой трахомудной rsa, просто добавит в заголовок:
              X-NSA-Submit: steam_password_in_plain_text


              А mitm-прокси всё передаст как есть, но вырежет X-NSA заголовок. А вот теортическая возможность коду в браузере проверить свою целостность — это вопрос удивительной глубокой философско-математической природы, не решённый в настоящий момент.

              0
              Похоже на упрощенную версию SRP-6, в которой пароль не покидает браузер и при регистрации тоже. Защищен от MITM, и от дыр в старом TLS и через HTTP можно, если нужно.
                +3

                Каким образом это защищает от mitm? Если я на каждую страницу дописываю <script src='mitm.js'>, то каким образом танцы в одном js защитят от другого?

                  0
                  Ну да, только от подслушивающего MITM.
                    +1

                    Это не mitm, а просто "перехват трафика". Mitm подразумевает возможность модификации трафика.

                    0
                    Вообще mitm.js тут ни при чем. SRP-6 про аутентификацию, а не доставку контента. Доставить код клиенту можно разными способами — подписанный exe, apk из google play, в конце концов можно попросить его написать код самому, на протокол аутентификации это не влияет.
                    А во время аутентификации по SRP-6 mitm не сможет прикинуться сервером не имея БД или прикинуться юзером, пронаблюдав за предыдущими сессиями. Так что да, защищает.
                  0
                  Возможно перекрытие ключей по времени нужно чтобы небыло ошибок авторизации при входе в конце часа. Думаю он несколько первых секунд часа должен позволять войти по старому ключу, чтобы компенсировать сетевые задержки.
                    0
                    Да нет, там же таймштамп ключа передается вместе с шифрованным паролем и если вы получили таймштамп ключа прошлого часа, то вы понимаете что попали на границу.
                    0

                    Интересно, чем их HTTP Digest Auth не устроила.

                      –1

                      Я был бы рад, если бы сервисы принимали не пароль, а хеш пароля который солится логином.


                      Ещё лучше — если после регистрации для того чтобы залогиниться нужно было сначала спросить у сервиса соль для логина и отправлять ему hash(hash(login+password)+salt). Тогда сервер может убедиться, что пользователь знает пароль, но если этот хеш логина будет перехвачен злоумышленником — ничего полезного он не получит, т.к. при следующем логине сервис выдаст уже другую соль для авторизации и перехваченный хеш не поможет.


                      Это помогало бы от того, что сервисы в итоге ломают и пароли утекают, т.к. либо слабо хешируются, либо хранятся в чистом виде, либо перехватываются, либо где-то в логах мелькают. А те, кто использует менеджеры паролей, скорее всего в меньшинстве и либо используют один пароль на всё, либо имеют какой-то паттерн.

                        0

                        Сомнительно…
                        Чем это эффективнее SSL?


                        Это помогало бы от того, что сервисы в итоге ломают и пароли утекают
                        так на сервере это и надо так хранить с солькю и спец. шешированием для паролей. но рутина с запрашиванием хеша у сервера сомнительна.
                          0
                          Как-то сделал на одном сервисе подобный логин через HTTP.
                          Единственный плюс: пароль пользователя дальше браузера не уплывает. Но толку-то? Если сервис взломают, или при MiM-атаке, могут вставить JS скрипт, а там уже все пароли уплывут.
                          Гораздо привычнее использовать обычный HTTPS с хешами и солью + двуфакторная авторизация, если кому надо.
                            0

                            В таком случае нужно хранить пароль в открытом виде, что само по себе так себе идея. Да, можно хранить условный hash(login+password), но это эквивалетно хранению пароля в открытом виде, т.к. для аутентификации в таком случае нужно знание не пароля, а этого самого hash(login+password).

                              0
                              Соль используют для защиты от быстрого перебора радужными таблицами.
                              По этому в предлагаемом варианте более верным и чуть более полезным будет вариант такой: hash(hash(login+pass+salt1)+salt2)
                              salt1 генерируется при создании/обновлении пароля
                              salt2 генерируется на каждую аутентификацию
                              При аутентификации передаются обе соли.
                            –1
                            Что мешает использовать простое XOR шифрование?
                            pass = 512bit
                            k_user = rand(512bit)
                            to_server = pass XOR k_user
                            k_server = rand(512bit)
                            to_user = to_server XOR k_server
                            to_server_clear = to_user XOR k_user
                            pass = to_server_clear XOR k_server

                            три пересылки
                            шифруется случайными значениями
                            не нужно RSA.js
                              +2
                              Посмотрев в исходник js на клиенте, перехваченный обмен можно расшифровать.
                              0

                              Автор сильно удивится что обычный md5 вместо пароля с меткой времени полученной с сервера и логином для повышения уникальности хешей сработал бы точно также

                                +5
                                выяснил, что 3600000000 микросекунд равно одному часу

                                Жаль, не раскрывается, каким образом автор это выяснил

                                  0
                                  Такая методика конечно помешает получить пароль в открытом виде, но она же совершенно не защищает от Man-in-the-middle, только от перехвата трафика? То есть полностью TLS эта система всё равно не заменит.
                                    –1
                                    Конечно.
                                    0
                                    mail.protonmail.com/login интересно делает, глянь ;)

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое