Соответствие реализации TOTP с Google Authenticator - PullRequest
1 голос
/ 07 апреля 2020

(Решение) TL; DR: Google предполагает, что строка ключа закодирована в base32; замена любых 1 на I и 0 на O. Это должно быть декодировано до хэширования.

Оригинальный вопрос

У меня проблемы с сопоставлением моего кода с GA. Я даже погнался за счетчиками +/- ~ 100 000 с текущего временного шага и ничего не нашел. Я был очень взволнован, увидев, что моя функция прошла тесты SHA-1 в Приложении RF C 6238, однако применительно к «реальной жизни» она, похоже, не сработала.

Я зашел так далеко, что посмотрел на открытый исходный код Google Authenticator на Github ( здесь ). Я использовал ключ для тестирования: "qwertyuiopasdfgh". Согласно коду Github:

  /*
   * Return key entered by user, replacing visually similar characters 1 and 0.
   */
  private String getEnteredKey() {
    String enteredKey = keyEntryField.getText().toString();
    return enteredKey.replace('1', 'I').replace('0', 'O');
  }

Я считаю, что мой ключ не будет изменен. Просматривая файлы, кажется, что ключ остается неизменным посредством вызовов: AuthenticatorActivity.saveSecret() -> AccountDb.add() -> AccountDb.newContentValuesWith().

Я сравнил свое время между тремя источниками:

  • (оболочка erlang): now()
  • (bash): date "+%s"
  • (Google / bash): pattern="\s*date\:\s*"; curl -I https://www.google.com 2>/dev/null | grep -iE $pattern | sed -e "s/$pattern//g" | xargs -0 date "+%s" -d

Они все одинаковые. Несмотря на это, кажется, что мой телефон немного от моего компьютера. Это изменит шаги не в синхронизации c с моим компьютером. Однако я, пытаясь отыскать нужный временной шаг на +/- тысячи, ничего не нашел. Согласно классу NetworkTimeProvider, это источник времени для приложения.

Этот код работал со всеми тестами SHA-1 в RF C:

totp(Secret, Time) -> 
%   {M, S, _} = os:timestamp(),

    Msg = binary:encode_unsigned(Time),   %(M*1000000+S) div 30,

    %% Create 0-left-padded 64-bit binary from Time
    Bin = <<0:((8-size(Msg))*8),Msg/binary>>,

    %% Create SHA-1 hash
    Hash = crypto:hmac(sha, Secret, Bin),

    %% Determine dynamic offset
    Offset = 16#0f band binary:at(Hash,19),

    %% Ignore that many bytes and store 4 bytes into THash
    <<_:Offset/binary, THash:4/binary, _/binary>> = Hash,

    %% Remove sign bit and create 6-digit code
    Code = (binary:decode_unsigned(THash) band 16#7fffffff) rem 1000000,

    %% Convert to text-string and 0-lead-pad if necessary
    lists:flatten(string:pad(integer_to_list(Code),6,leading,$0)).

Для это действительно соответствует RF C, его нужно изменить для 8-ди git номеров выше. Я изменил его, чтобы попытаться найти правильный шаг. Цель состояла в том, чтобы выяснить, как мое время было неправильно. Не получилось:

totp(_,_,_,0) ->
    {ok, not_found};
totp(Secret,Goal,Ctr,Stop) -> 
    Msg = binary:encode_unsigned(Ctr),
    Bin = <<0:((8-size(Msg))*8),Msg/binary>>,

    Hash = crypto:hmac(sha, Secret, Bin),
    Offset = 16#0f band binary:at(Hash,19),

    <<_:Offset/binary, THash:4/binary, _/binary>> = Hash,

    Code = (binary:decode_unsigned(THash) band 16#7fffffff) rem 1000000,

    if Code =:= Goal ->
        {ok, {offset, 2880 - Stop}};
    true ->
        totp(Secret,Goal,Ctr+1,Stop-1) %% Did another run with Ctr-1
    end.

Что-нибудь очевидное торчало?

1 Ответ

1 голос
/ 07 апреля 2020

У меня возникло желание создать собственное приложение Android для реализации TOTP для моего проекта. Я продолжал смотреть на код Java. С помощью загрузки репозитория git и grep -R для поиска вызовов функций я обнаружил свою проблему. Чтобы получить те же пин-коды, что и в Google Authenticator, предполагается, что ключ закодирован в base32 и должен быть декодирован до передачи его алгоритму ha sh.

В getEnteredKey() был намек на это путем замены символов 0 и 1, так как они отсутствуют в алфавите base32.

...