Немного воды или, что же автор хотел всем этим сказать?
В преамбуле к статье я писал, что занимаюсь дурью проектирую протокол беспарольной аутентификации в Web, который бы сильно облегчил всем нам (конечным пользователям сайтов) жизнь, избавляя от парольного ада. И эта статья написана, чтобы «раскрывать причины технических решений, закладываемых в новый протокол». Так вот, касаемо схемы авторизации, я делаю ставку на SRP.
На самом деле протоколов авторизации гораздо больше, чем было описано выше. Но SRP – это, на мой взгляд, самая крутая схема, созданная профильным специалистом, выпускником Стэндфордского университета (Thomas Wu), и проверенная его коллегами и другими специалистами по безопасности.
Возникает закономерный вопрос? Если эта схема такая крутая и древняя, почему она не получила до сих пор широкого распространения.
Я думаю, что этот протокол просто опередил свое время. Тогда аутентификацию предполагалось выполнять на клиенте в браузере. А javascript находился в зачаточном состоянии и мог, в лучшем случае вывести алертом «hello word». А нужна была арифметика на больших числах. Сейчас уже есть, но время упущено.
И самое главное, веб сообщество до сих пор не осознало, что процесс аутентификации необходимо выносить за рамки прямого взаимодействия сервер–браузер (равно как и аутентификацию в иных системах). В самом деле, ведь никто в здравом уме не хотел бы реализовывать вручную TLS (на javascipt/PHP/JAVA)?
И если это осознать, то не трудно видеть, что гораздо более правильным решением, поступить также с аутентификацией: создать протокол более низкого уровня, заточить его на использование парольных менеджеров (как сейчас менеджеры сертификатов и криптопровайдеры).
Hmac (код авторизации сообщений на основе хэша)
HMAC расшифровывается как Hash-based message authorization code — код авторизации сообщений на основе хэша и является более строгим типом аутентификации, более распространенным в финансовых API. В HMAC только отправитель, и получатель знают секретный ключ, который больше неизвестен никому.
Затем сообщение кодируется секретным ключом и проходит через алгоритм безопасного хеширования (SHA — secure hashing algorithm). (Хеш — это зашифрованная строка на основе алгоритма.) Результирующее значение, называемое сигнатурой, помещается в заголовок запроса.
Сервер API (получатель), получая запрос, принимает те же системные свойства (отметка времени запроса плюс идентификатор учетной записи) и использует секретный ключ (который известен только запрашивающей стороне и серверу API) и SHA для генерации одной и той же строки. Если строка соответствует подписи в заголовке запроса, запрос принимается. Если строки не совпадают, запрос отклоняется.
Вот диаграмма, отображающая процесс авторизации HMAC:
Важным моментом является то, что секретный ключ (критический для восстановления хэша) известен только отправителю и получателю. Секретный ключ не включается в запрос. Безопасность HMAC используется, когда нужно убедиться, что запрос является подлинным и не может быть подделан.
Что документируется в разделе аутентификации
В документации API не нужно подробно объяснять внешним пользователям, как работает аутентификация. Отсутствие объяснений внутренних процессов аутентификации, является лучшей практикой, поскольку хакерам будет сложнее злоупотреблять API.
Тем не менее нужно объяснить необходимую информацию:
- как получить API ключ;
- как пройти аутентификацию запроса;
- сообщения об ошибках, связанных с неверной аутентификацией;
- чувствительность информации аутентификации;
- период действия токена доступа (авторизации).
Если есть открытый и закрытый ключи, нужно объяснить, где следует использовать каждый ключ, и отметить, что закрытые ключи не должны использоваться совместно. Если разные уровни лицензий предоставляют разный доступ к вызовам API, эти уровни лицензирования должны быть явно указаны в разделе авторизации или в другом месте.
Поскольку раздел API ключей важен, и нужен разработчикам до того, как они начнут использовать API, этот раздел должен быть в начале руководства.
А кстати, почему это srp вдруг крут?
Чтобы понять это, необходимо вначале глужбе его изучить. Сравнить с другими методами и протоколами.
Злоумышленник не сможет сделать ничего, даже если взломал сервер и активно контролирует канал. Это очень круто.
Хешируем
Ну хорошо. Попробуем усилить элементарную схему. Первое, что приходит на ум: будем передавать не пароль P, а его хэш H. И сервер будет хранить не исходный пароль, а его хеш. Теперь исходный пароль пользователей не сможет узнать ни MitM, ни взломщик БД.
Для надежности, возьмём в качестве хеширующей функции SHA2 или SHA3. 256-битов на текущий момент вполне достаточно. Но если хотите – обе функции поддерживают режим 512 бит. Хотя признаться, такое количество бит потребует сделать в БД поле размером в 64 байта!
Хорошо. Вот только злоумышленнику MitM не нужно уже знать пароль – ему достаточно узнать его хэш. Мало того, если пользователь использует такой же пароль на другом сайте, и тот сайт использует подобную «защиту», то MitM и там может «поживиться» (ему уже не обязательно взламывать канал, достаточно зайти «легально»).
Солим
В терминах криптографии, «соль» – это последовательность случайных бит, которые добавляются к хэшируемой (или шифруемой) последовательности для «рандомизации» результата. Добавляя соли к паролям при хешировании, мы будем получать разные хэши для одинаковых паролей. Ну при условии, конечно же, что соль используется разная:
Scrypt’им
В принципе, описанная выше схема уже надежна в некоторой степени. И уж более надежна чем предыдущие. Но тут есть одна засада.
Производительность компьютеров растет. И хотя закон Мура давно как не действует (по слухам, с 2006 года), и рост уже не экспоненциальный, на «плато» мы ещё не вышли. То что ранее считалось невозможным, теперь доступно многим. Например, аренда на время вычислительных кластеров с фантастической мощностью.
поговаривают, что
количество хешей, которое обсчитывается в сети Bitcoin каждые три секунды, достаточно, чтобы найти коллизию для SHA-1. Источник.
Теперь взломщик БД может попытаться восстановить пароли интересующих его учетных записей, даже если они захешированы с солью (все восстанавливать нет смысла и не хватит времени).
Как? В начале по таблице известных паролей: синтетических qwerty, популярных 123 и когда-то угнанных реальных. Затем, путём тупого перебора символов из заданного алфавита. Алгоритм концептуально прост: берем пароль-кандидат, вычисляем hash( password, salt ), сравниваем результат. Соль учетной записи известна, ибо хранится рядом с хешем пароля.
На нашу беду, процесс перебора хорошо параллелится. А значит, в теории рост производительности не ограничен (правда только в теории; практика – вещь куда более суровая).
Вот для примера расчеты
Этой проблемой озадачились уже достаточно давно. И в результате усилий криптографов, на свет появилась такая функция как scrypt.
Функция scrypt делает почти тоже самое, что и хэш-функция (на самом деле использует в качестве базы SHA2-256), но создана таким образом, чтобы усложнить атаку перебором при помощи ПЛИС: FPGA, ASIC и подобных. Она заставляет использовать в алгоритме много циклов и ветвлений (чего суперскаллярные процессоры крайне не любят), к тому же, требует слишком много памяти на одно ядро.
Поэтому, вместо стандартной хеширующей функции, мы будем использовать scrypt. WebCryptApi пока её не поддерживает, но для javascript-разработчиков есть уже реализованные библиотеки. В остальном схема взаимодействия пока остается прежней. И вроде бы, надежной.
Но защитит ли она от пассивного MitM? Нет. Ибо мы всё ещё посылаем на сервер всегда один и тот же хеш. А значит, перехватив передачу итогового хеша на сервер, злоумышленник может в последствии легально его использовать для входа под чужой учеткой.
Добавляем challenge
Ну хорошо. Давайте усложним жизнь и для MitM. Попытаемся добиться того, чтобы хеш от пароля на сервер передавался всегда разным. Как? А давайте «посолим» уже сам хэш! Тот самый, что нам дает функция scrypt( password, salt ). Для этого сервер сгенерирует и пошлет клиенту случайную последовательность байт (длина которой равна длине блока хеширующей функции), которую мы (по традиции) назовем challenge. Теперь схема взаимодействия клиента и сервера такова:
Обоюдный challenge
Казалось бы где тут подвох? Да почти нигде. Вот только, что если наш злоумышленник MitM вклинится в момент аутентификации и поменяет challenge от сервера на более простой (например – все нулевые биты, или вообще пустой)? Зачем? А чтобы попытаться облегчить себе задачу обратного восстановления H из Hs.
Защититься от такой потенциальной атаки можно, если клиент будет генерировать свой challenge и использовать его для хеширования пароля. Теперь на шаге 2, клиент вычисляет Hs = hash(H,server-challenge,client-challenge) и отправляет Hs вместе со своей версией challenge.
Финальный штрих
Некоторые, возможно, уже заметили, что даже такая усложненная схема авторизации не защищает нас от взломщика БД. Последний может использовать компрометированные хеши пользователей и соли для легального входа от имени пользователей. Можно ли как-то от этого защититься?
Можно. И даже более того. Фактически мы можем сделать так, что угон аутентификационных данных пользователей будет бессмысленным. Компрометация этих сведений не принесет взломщику ничего (ну правда, он может «поживиться» куда более ценными сведениями; например данными сохраненных кредитных карт и cvc-кодами для них; но мы ведь защищаем процессы авторизации, а не проектируем PCI DSS).
Чтобы защититься от компрометации на стороне сервера, клиенту необходимо иметь некий секрет и алгоритм доказательства серверу, что клиент знает этот секрет. Тут на ум сразу же приходит криптография на ассиметричных ключах. Попробуем вначале обойтись хешированием или симметричными шифрами. И посмотрим как это можно реализовать.
КРАЙНЕ ВАЖНОЕ ЗАМЕЧАНИЕ: если вдруг кому-то покажется интересной какая-то из двух рассмотренных далее схем, ни в коем случае не спешите внедрять. Внимательно изучите сравнительную таблицу недостатков и уязвимостей. Опять же, не забывайте про опасность использования «учебных» криптопротоколов. Я предупредил.
А Схема защиты на симметричных ключах
Пусть H – это тот самый хеш клиента, по которому сервер его аутентифицирует. H хранится на сервере. H может быть потенциально стать доступным взломщику БД.
Пусть клиент имеет некий ключ K, которым он зашифровывает свой исходный аутентификационный хеш H: V = EK(H), где E – функция шифрования на ключе K. Итоговый шифр V вместе с H сервер хранит в своей БД. Если алгоритм шифра выбран надежный, то из пары (V, H) восстановить исходный ключ почти невозможно.
Теперь в процессе аутентификации нам остается передать защищенным образом пару (H, V) серверу. Тот сверит H, расшифрует V и убедится, что мы действительно знаем ключ К, а не украли H при взломе БД.
Это был концепт. Теперь конкретика.
Определим функцию hmac как функцию HMAC-SHA2-256. Salt и challenge должны быть равны длине блока хеширующей функции. В нашем случае – 256 бит. Определим функцию E – как функцию шифрования, а функцию D – как функцию расшифровывания на базе симметричного шифра AES-256. Наконец, определим функцию scrypt, как функцию, возвращающую 256-битный хеш от пароля и его соли.
На стороне клиента производятся предварительные вычисления:
Защитим исходный пароль пользователя, вычислив
.
Сформируем аутентификационный хеш
.
Сформируем код подтверждения .
Сформируем шифр аутентификационного хеша
.
При регистрации клиент посылает серверу пару (H, V) в открытом виде. Заметим, что сервер не знает ни исходный пароль пользователя, ни его первичный хеш Hp.
Алгоритм авторизации клиента теперь такой:
Amazon web services
авторизация амазон
Amazon использует HMAC. Процесс достаточно сложный, чтобы включить полноценную диаграмму, показать шаги, которые должны выполнить пользователи.
Basic auth
Другой тип авторизации называется Basic Auth. С помощью этого метода отправитель помещает пару имя пользователя:пароль в заголовок запроса. Имя пользователя и пароль кодируются с помощью Base64, который представляет собой метод кодирования, который преобразует имя пользователя и пароль в набор из 64 символов для обеспечения безопасной передачи. Вот пример Basic Auth в заголовке запроса:
Dropbox
Авторизация в Dropbox
Глава 4. аутентификация, часть 1
Аутентификация с помощью ключа API — это метод, который преодолевает слабость использования общих учетных данных, требуя доступа к API с помощью уникального ключа. В этой схеме ключ обычно представляет собой длинную последовательность букв и цифр, отличную от пароля для входа владельца учетной записи. Владелец дает ключ клиенту, так же как отель дает гостю ключ от одноместного номера.
Когда клиент аутентифицируется с помощью ключа API, сервер понимает, что можно разрешить клиенту доступ к данным, но теперь имеет возможность ограничить административные функции, такие как изменение паролей или удаление учетных записей. Иногда ключи используются просто, чтобы пользователю не приходилось выдавать свой пароль. Гибкость обеспечивается аутентификацией API Key для ограничения контроля, а также защиты паролей пользователей.
Итак, где же находится ключ API? Заголовок для него тоже есть, да? К сожалению, нет. В отличие от Basic Auth, который является установленным стандартом со строгими правилами, ключи API были разработаны в нескольких компаниях на заре интернета. В результате аутентификация с помощью ключа API немного похожа на дикий запад; у каждого свой способ делать это.
Однако со временем появилось несколько общих подходов. Один из них заключается в том, чтобы клиент поместил ключ в заголовок Авторизации вместо имени пользователя и пароля. Другой — добавить ключ в URL-адрес (http://example.com? api_key=my_secret_key ). Реже ключ закапывают где-нибудь в теле запроса рядом с данными. Куда бы ни пошел ключ, эффект будет одинаковым — он позволяет серверу аутентифицировать клиента.
Ложка дёгтя в бочке мёда.
Есть одна проблема – математическая. Эллиптические кривые используются в криптографии относительно недавно и ещё слабо изучены. Даже сами математики говорят, что теория эллиптических кривых только развивается. Не случайно, что самые крупные достижения в математике последних лет (теорема о модулярности эллиптических кривых, приведшая к доказательству известного Диафантова уравнения, [всё ещё спорное] доказательство ABC-гипотезы) тесно связаны с эллиптическими кривыми.
Известно также, что существуют классы эллиптических кривых, которые являются слабыми. Однако на сегодняшний день не известно методик, которые бы однозначно могли оценить надёжность кривых. А вот методики, которые позволяют генерировать слабые кривые, замаскированные под сильные, есть.
Обнадёживает тут только то, что даже если эллиптическая кривая «подведёт», пароль пользователя всё ещё находится под надёжной защитой благодаря scrypt и hmac.
Образцы разделов авторизации
Ниже приведены несколько примеров разделов авторизации в документации API.
Определяем термины
Во-первых, давайте определимся с некоторыми ключевыми терминами:
- Аутентификация: подтверждение правильности личности
- Авторизация: разрешение определенного действия
API может аутентифицировать, но не разрешит делать определенный запрос.
аутентификация и авторизация
Разные виды авторизации
Существует несколько методов авторизации. Ниже рассмотрим несколько вариантов авторизации, которые встречаются чаще всего:
Схема авторизации на ассиметричных ключах и эллиптических кривых
Пусть в процессе аутентификации клиент подпишет случайную строку message своим закрытым ключом e. А сервер проверит подпись клиента, зная его открытый ключ D. Но если клиент может таким образом доказать, что ключ D принадлежит ему, и только ему, то нам уже не нужно «заморачиваться» с хранением хэша от пароля!
Схема на базе протокола srp
SRP — протокол парольной аутентификации, устойчивый к прослушиванию и MITM-атаке и не требующий третьей доверенной стороны. Имеет свой RFC2945. Есть производные от него стандарты для протоколов Telnet, TLS. Единственный из представленных, который проводит окончательную взаимную авторизацию обеих сторон. Хорошо описан как в Википедии, так и на Хабре пользователем @datacompboy.
Тем не менее, для генерации секретного ключа x протокол использует устаревшие подходы: (уже не криптостойкую) функцию SHA-1 и простую схему хеширования: x = hash(salt | hash(login | ‘:’ | pass). В свете современных тенденций к взлому паролей брутфорсом офлайн на специализированных ПЛИС или арендованных кластерах, данный подход должен быть улучшен, использованием scrypt.
Для реализации схемы нам понадобятся: большое простое число N, генератор g мультипликативной группы Z*N и параметр k = hash(N, g). В качестве g и N стороны используют числа, определенные в RFC-5054. Так, например, g = 2, а «мультипликативная группа» – это просто степени двойки; точнее их остатки по модулю N.
Процесс регистрации пользователя в системе:
Клиент отправляет серверу свой login и свою 256-битную версию соли saltC.
Сервер генерирует свою 256-битную версию соли saltS для пользователя login.
Клиент формирует общую 256-битную соль: salt = saltC ^ saltS.
Клиент вычисляет секретный 256-битный ключ x = scrypt(password, salt) от своего пароля
Клиент вычисляет верификатор пароля V = gx (mod N).
Клиент посылает на сервер пару (saltC, V).
Сервер вычисляет общую соль salt аналогично клиенту в п. 3.
Сервер хранит учетные данные пользователя в форме: login, salt, V.
Процесс авторизации пользователя в системе:
Клиент формирует случайное 256-битное число a и вычисляет 2048-битное A = ga (mod N).
Клиент делает запрос на сервер со своим login, и числом А.
Сервер формирует случайное 256-битное число b и вычисляет 2048-битное B = (kv gb) (mod N).
Сервер посылает клиенту пару (salt, B).
Обе стороны вычисляют u = hash(A | B). В оригинальном RFC указано, что параметр u – 32-битное беззнаковое, получаемое от результата примененной хеш-функции hash(A | B).
Пользователь вычисляет свой ключ x на основе пароля и соли, аналогично пп. 4 процесса регистрации.
Пользователь вычисляет сессионный ключ K следующим образом: S = (B – kgx)a ux (mod N); K = hash(S).
Сервер вычисляет сессионный ключ K следующим образом: S = (Avu)b (mod N); K = hash(S).
Пользователь вычисляет M1 = hash( hash(N) ^ hash(g) | hash(login) | salt | A | B | K ) и отправляет на сервер.
Сервер дождавшись от пользователя M1, проверяет его, производя аналогичные п. 9 вычисления.
Сервер вычисляет M2 = hash( A | M1 | K ) и отправляет клиенту.
Клиент проверяет M2 сервера. На этом этапе аутентификация считается завершенной.
В функции hash операция | означает конкатенацию аргументов. Если предполагается, что ключ K не будет использоваться в дальнейшем взаимодействии (например, для шифрования или аутентификации пакетов), то пп. 9-12 к исполнению обязательны. А если будет, то проверка излишняя, т.к. несогласованный ключ не позволит проводить корректное взаимодействие. Но я бы всё-таки оставил эти пункты обязательными.
Согласно спецификации, пользователь должен прервать аутентификацию, если получил B = 0 (mod N) или u = 0. (Полагаю, что B не должно быть также равно –kV, т.к. в этом случае клиент получит S = 0a ux (mod N) = 0).
Сервер должен прервать аутентификацию, если получил A = 0 (mod N). Клиент должен первым доказать, что получил тот же ключ K. Если клиенту это не удалось, сервер должен прервать аутентификацию, без своего доказательства K.
👨💻 практическое занятие: авторизация
В своем найденном опен-сорс проекте найдем информацию об авторизации для запросов к API. Ответьте на следующие вопросы:
- Какого рода авторизация требуется для отправки запросов к API?
- Существуют ли разные уровни доступа в рамках авторизации (например, платные и бесплатные), которые определяют, сколько запросов можно сделать или какие типы информации можно получить?
- Можно ли вы получить ключ API или какой-либо другой метод авторизации, необходимый для выполнения тестовых вызовов API?
- Как информация об авторизации интегрируется в раздел «Начало работы»?
Go next ➡
Sendgrid
API ключ SendGrid
SendGrid предлагает подробное объяснение ключей API, начиная с основ, поясняя: «Что такое ключи API?». Контекстно раздел ключей API появляется вместе с другими разделами по управлению учетными записями.
Подведём итоги
Параметр / Схема | 1. Симметричная на базе HASH | 2. На базе ECDSA | 3. SRP-6a |
Скорость | Проверка на сервере реализуется быстрее | Проверка подписи на стороне сервера в 2 раза медленнее клиента | Медленная, требует длинной арифметики и 4 пакета для завершения аутентификации |
Технические сложности | Отсутствуют; может быть реализована на нативном js; работает достаточно быстро. | Отсутствует поддержка произвольной кривой в WebCryptoApi на клиенте | Уже никаких |
Потенциальные уязвимости | Уязвима к компрометации БД и последующему контролю канала сервера; уязвима в процессе регистрации пользователя | Существуют классы уязвимых эллиптических кривых. Методики оценки безопасности кривых ещё не разработано. Вероятность, что выбрана «слабая» кривая, всегда остается. | Задача решения дискретного логарифма может быть решена теоретически; надежность базируется на сложности факторизации N; числа N, g, k известны всем. |
Обратить особое внимание на | Случайное число k, генерируемое клиентом в процессе подписи, не должно использоваться дважды! | Числа A, B не должны быть равны 0 и 0, –vk соответственно. | |
Защита от пассивного MitM | да (но только не в момент регистрации) | да | да |
Защита от активного MitM | да (только, если учетные данные не компрометированы) | да | да |
Защита пароля при компрометации БД | да | да | да |
Защита после компрометации БД и прослушивании канала | нет | да | да |
Препятствие «легальному» доступу злоумышленника после компрометации БД | да | да | да |
Взаимная аутентификация сторон | нет | нет | да |
Защита от фишинга (попытка злоумышленника выдать себя за легальный сервер) | нет | нет | да |
Требует наличие какого-либо доп. защищенного канала при регистрации клиента? | да | нет | нет |
Защита слабых паролей в момент авторизации | нет | да | да |
Защита от владельцев ЦОД | нет | нет | нет |
Вход в личный кабинет