首页 > 解决方案 > 将 TOTP 实现与 Google Authenticator 匹配

问题描述

(解决方案)TL;DR: Google 假定密钥字符串是 base32 编码的;1I0替换任何O。这必须在散列之前解码。

原始问题

我很难让我的代码与 GA 匹配。我什至从当前时间步开始追逐 +/- ~100,000 的计数器,但一无所获。看到我的函数通过了 RFC 6238 附录中的 SHA-1 测试,我感到非常兴奋,但是当应用于“现实生活”时,它似乎失败了。

我什至在 Github 上查看了 Google Authenticator 的开源代码(这里)。我使用密钥进行测试:"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().

我比较了三个来源之间的时间:

他们都是一样的。尽管如此,我的手机似乎离我的电脑有点远。它将更改与我的计算机不同步的步骤。但是,我试图将正确的时间步长 +/- 数千次,但什么也没找到。根据NetworkTimeProvider班级,这是应用程序的时间源。

此代码适用于 RFC 中的所有 SHA-1 测试:

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)).

为了使其真正匹配 RFC,需要针对上面的 8 位数字进行修改。我对其进行了修改以尝试追踪正确的步骤。目标是弄清楚我的时间是怎么错的。没有解决:

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.

有什么明显的突出吗?

标签: erlangone-time-passwordgoogle-authenticatortotp

解决方案


我很想制作自己的 Android 应用程序来为我的项目实施 TOTP。我确实继续查看 Java 代码。借助下载 git 存储库并grep -R查找函数调用,我发现了我的问题。要获得与 Google Authenticator 相同的密码,密钥被假定为 base32 编码,并且必须在将其传递给哈希算法之前对其进行解码。

getEnteredKey()通过替换0and字符来暗示这一点,1因为这些在 base32 字母表中不存在。


推荐阅读