- PVSM.RU - https://www.pvsm.ru -

Как работают одноразовые пароли

Как работают одноразовые пароли

Вступление

Как показывает практика, существует определенное непонимание принципов работы одноразовых паролей (это те самые, которые используются в GMail, в спец. токенах платежных систем и так далее).

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

Хэш-функция

Хэш-функция [1] позволяет взять любые данные любой длины и построить по ним короткий «цифровой отпечаток пальца». Длина значения хэш-функции не зависит от длины исходного текста; например, в случае популярного алгоритма SHA-1 [2] длина этого отпечатка составляет 160 бит.

Чтобы понять, почему значение всегда имеет одинаковую длину и не зависит от исходного текста, можно упрощенно представить хэш-функцию в виде кодового замка с колесиками. Вначале мы выставляем все колесики в «ноль», затем идем по тексту и для каждой буквы прокручиваем колесики в соответствии с некоторыми правилами. То число, которое окажется на замке в конце, и есть значение хэш-функции. Примерами таких функций являются MD5, SHA-1, ГОСТ_Р_34.11-94.

Не придумывайте свои хэш-функции, используйте стандартные реализации (например, в случае Python):

import hashlib
print hashlib.sha1("Hello, Bob!").hexdigest()

Результат: 88192e3e2e83243887410897efd90287b8e453a7

Идея хэш-функции в том, что она работает только в одном направлении: ее очень легко подсчитать для «Войны и мира», но практически невозможно по уже готовому значению хэш-функции найти документ, который даст такое же значение. Даже если изменить в документе всего одну букву, хэш изменится полностью [3]:

import hashlib
print hashlib.sha1("Hello, Bob?").hexdigest()

Результат: cbad4b0e05703acf2e8572be7438830fe7f8ddf5

В связи с этим возникает естественное желание использовать хэш-функцию для контроля целостности сообщений, которые Алиса посылает Бобу: Алиса подсчитывает для каждого своего сообщения значение SHA-1 и вкладывает его в конверт; Боб, самостоятельно подсчитав SHA-1 текста, может сравнить свой результат с Алисиным и удостовериться, что сообщение не было изменено где-то по дороге.

Однако мы забыли о Меллори, который находится где-то между Алисой и Бобом, перехватывает их переписку и вскрывает конверты! Он вполне может изменить сообщение, после чего подсчитать для него SHA-1 и приложить к письму; Боб сверит значения и ничего не заметит.

Проверка подлинности

Подумав, Алиса и Боб при встрече договариваются, что при подсчете SHA-1 они будут временно дописывать к тексту секретное слово, например, «Secret» (конечно, в реальности Алиса и Боб решили использовать куда более длинное слово, чтобы его было сложно подобрать). Меллори не знает это слово, а следовательно, даже если изменит сообщение, то не сможет скорректировать его электронную подпись.

Пример:

import hashlib
print hashlib.sha1("Secret" + "Hello, Bob").hexdigest()

Результат: 99beeff3ef1971d2cb1be129f986739f6bcba8cc

К сожалению, тут есть проблемы. Да, Меллори не может изменить тело сообщения, но (раз он знает хэш от текущего текста) он всегда может дописать в конце «P.S. На самом деле все это чушь, нам пора расстаться, Боб» и просто досчитать хэш от остатка (вспомним аналогию с кодовым замком).

Чтобы защититься от этого, мы немного усложним нашу функцию:

print hashlib.sha1("Secret" + hashlib.sha1("Hello, Bob").hexdigest()).hexdigest()

Результат: 3f51e9fc540676bc3ce54367fd3e467f3299c743

Теперь дописывание чего-либо в конец сообщения полностью изменит исходные данные для «внешнего» вызова SHA-1 и Меллори остается вне игры.

Алиса и Боб только что придумали то, что называется HMAC (или hash-based message authentication code [4]): основанный на хэш-функции код проверки подлинности сообщений. В реальности HMAC, принятый как стандарт RFC2104 [5] выглядит чуть-чуть сложнее за счет выравнивания длины ключа и пары XOR'ов внутри, но суть не меняется.

Не придумывайте свои реализации HMAC, используйте стандартные реализации, например, HMAC-SHA1:

import hmac
import hashlib
print hmac.new(key="Secret", msg="Hello, Bob", digestmod=hashlib.sha1).hexdigest()

Одноразовые пароли

Что такое "одноразовый пароль [6]"? Это пароль, который бесполезно перехватывать с помощью кейлоггера, подглядывания через плечо или прослушивания телефонной линии — т.к. этот пароль используется ровно один раз.

Как можно было бы реализовать эту схему? Например, Алиса может сгененировать сотню случайных паролей и отдать копию Бобу. Когда Боб позвонит в следующий раз, он продиктует самый верхний пароль в списке, Алиса сверит его со своим, после чего оба вычеркнут его. При следующем звонке они используют очередной пароль и так далее, пока они не закончатся. Это не очень удобно: хранение списков, генерация новых паролей и так далее.

Лучше реализовать эту схему в виде алгоритма. Например, паролем является его номер по порядку, умноженный на секретное число. Пусть Алиса и Боб договорились, что секретным числом является 42; тогда первым паролем будет 42, вторым 84, третьим 106 и так далее. Меллори, не знающий алгоритма и секретного числа, никогда не догадается, какой пароль будет следующим!

Конечно, алгоритм лучше выбрать посложнее. Алиса вспоминает про HMAC и предлагает Бобу считать пароль номер N по формуле: HMAC(«Secret», номер-пароля). После этого им нужно договориться о ключе (в данном случае это «Secret»), зато потом Бобу нужно только помнить, какой по счету пароль он генерирует (например, двадцатый):

import hmac
import hashlib
print hmac.new(key="Secret", msg="20", digestmod=hashlib.sha1).hexdigest()

Результат: 393e9efaae1a687bc2dcc257c8e9e2a61f26fe4b

Впрочем, Бобу совсем не улыбается каждый раз диктовать такой длинный пароль. Они с Алисой договариваются, что будут использовать только его часть, например, последние 6 символов.

Некоторое время все идет хорошо. До момента, пока Бобу и Алисе не надоедает вести подсчет, какой по счету пароль они используют. Кто-то подсказывает им, что в качестве аргумента HMAC() вместо номера можно использовать все, к чему Алиса и Боб имеют одновременный доступ… например, текущее время!

Наши герои синхронизируют свои часы и договариваются, что будут в качестве аргумента HMAC() использовать unix time — количество секунд, прошедших с момента наступления эпохи UNIX [7] (в UTC). Чтобы вводить пароль не торопясь, они решают разделить время на 30 секундные «окна»; таким образом, на протяжении 30 секунд действует один и тот же пароль. Естественно, Алиса, проверяющая пароли, в течение 30 секунд не позволяет использовать пароль повторно (просто запоминая его) и тем самым оставляет его по-настоящему «одноразовым».

Теперь пароль вычисляется по следующей формуле: HMAC(«Secret», unix_timestamp / 30).

Мы получили одноразовые пароли на основе текущего времени [8]. Сгенерировать и проверить эти пароли может только тот, кто обладает ключом («Secret» в примере выше); иначе говоря, сервер и пользователь.

Следует отметить, что одноразовые пароли могут считаться и по другим алгоритмам; главное, чтобы алгоритм и секрет были известны обеим сторонам. Но т.к. у нас есть стандарт, дальше мы будем говорить именно о нем

OATH, TOTP, HOTP, RFC… WTF?

Итак, мы только что описали основные идеи, лежащие в основе:

1) HMAC, hash-based message authentication code: RFC2104 [9]
2) HOTP, hash-based one-time password: RFC4226 [10]
3) TOTP, time-based one-time password: RFC6238 [11]

Эти идеи — один из краеугольных камней инициативы Initiative For Open Authentication [12] (OATH), направленной на стандартизацию методов аутентификации.

Двухэтапная аутентификация Google

Одноразовые пароли, основанные на времени (и подсчитываемые на основе алгоритма TOTP RFC 6238) используются также компанией Google в приложении Google Authenticator [13], которое можно установить на iOS, Android, BlackBerry. Это приложение автоматически генерирует одноразовые пароли раз в 30 секунд (в дополнение к основному паролю на Google Account). Это означает, что даже если Ваш основной пароль кто-то подглядит или перехватит, без очередного одноразового пароля в систему войти будет невозможно. Удобно.

ВНИМАНИЕ: Я НЕ НЕСУ НИКАКОЙ ОТВЕТСТВЕННОСТИ ЗА ВАШИ ДЕЙСТВИЯ С ВКЛЮЧЕНИЕМ И ВЫКЛЮЧЕНИЕМ ДВУХЭТАПНОЙ АУТЕНТИФИКАЦИИ GOOGLE; ВЫ СОГЛАСНЫ, ЧТО ВЫ ВЫПОЛНЯЕТЕ ИХ НА СВОЙ СТРАХ И РИСК.

На самом деле там нет ничего страшного (есть инструкции, есть trusted-компьютеры, есть резервные коды и т.д.), но если от души постараться, бездумно нажимая на кнопки, то вполне можно лишиться доступа к своему аккаунту. И все же: если не готовы экспериментировать, не трогайте Gmail; просто скачайте приложение Google Authenticator на телефон, вручную добавьте «ключ по времени» (например, «a abc def abc def abc» или просто отсканируйте QR-код ниже).

Для начала нам нужно получить секретный ключ, который используется для создания одноразового пароля. Его можно посмотреть на странице добавления Google Authenticator'а в настройках аккаунта, он находится под QR-кодом:

Как работают одноразовые пароли

Обратите внимание, что если двухэтапная аутентификация уже включена, то старый ключ узнать нельзя: придется удалить старый и сгенерировать новый. Это несложно; главное, не забыть сразу обновить ключ и в Google Authenticator, если Вы им пользуетесь.

Ключ закодирован в Base32 [14] (для удобства уберите пробелы и переведите буквы в верхний регистр).

Программа, которая подсчитывает текущий одноразовый пароль:

import time
import hmac
import hashlib
import base64

### TOTP-key (get it from Google)
secret = base64.b32decode("AABCDEFABCDEFABC")

### Calc counter from UNIX time (see RFC6238) 
counter = long(time.time() / 30)

### Use counter as 8 byte array
bytes=bytearray()
for i in reversed(range(0, 8)):
  bytes.insert(0, counter & 0xff)
  counter >>= 8

### Calculate HMAC-SHA1(secret, counter)
hs = bytearray(hmac.new(secret, bytes, hashlib.sha1).digest())

### Truncate result (see RFC4226)
n = hs[-1] & 0xF
result = (hs[n] << 24 | hs[n+1] << 16 | hs[n+2] << 8 | hs[n+3]) & 0x7fffffff

### Print last 6 digits
print str(result)[-6:]

Теперь можно положить рядом запущенный Google Authenticator и сравнить значения.

Выводы

  1. Одноразовые пароли — это несложно.
  2. Стандарты — это хорошо.
  3. При желании их можно встроить и в свое приложение (используйте готовые библиотеки).

Автор: vk2


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/informatsionnaya-bezopasnost/16876

Ссылки в тексте:

[1] Хэш-функция: http://ru.wikipedia.org/wiki/Хеш-функция

[2] SHA-1: http://ru.wikipedia.org/wiki/SHA-1

[3] изменится полностью: http://ru.wikipedia.org/wiki/Лавинный_эффект

[4] hash-based message authentication code: http://ru.wikipedia.org/wiki/HMAC

[5] RFC2104: http://www.faqs.org/rfcs/rfc2104.html

[6] одноразовый пароль: http://ru.wikipedia.org/wiki/Одноразовый_пароль

[7] эпохи UNIX: http://ru.wikipedia.org/wiki/UNIX-время

[8] одноразовые пароли на основе текущего времени: http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm

[9] RFC2104: http://tools.ietf.org/html/rfc2104

[10] RFC4226: http://tools.ietf.org/html/rfc4226

[11] RFC6238: http://tools.ietf.org/html/rfc6238

[12] Initiative For Open Authentication: http://www.openauthentication.org

[13] Google Authenticator: http://support.google.com/accounts/bin/answer.py?hl=ru&answer=1066447

[14] Base32: http://en.wikipedia.org/wiki/Base32