(Решение) 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.
Что-нибудь очевидное торчало?