Google 身份验证器代码与服务器生成的代码不匹配

Posted

技术标签:

【中文标题】Google 身份验证器代码与服务器生成的代码不匹配【英文标题】:Google Authenticator code does not match server generated code 【发布时间】:2016-03-04 06:20:00 【问题描述】:

背景


我目前正在开发一个双因素身份验证系统,用户可以使用他们的智能手机进行身份验证。在用户可以使用他们的设备之前,他们需要先验证它。为此,他们需要扫描我给他们的二维码并输入随后显示的代码。

问题


QR 码的扫描工作正常,并且可以被 Google Authenticator 应用程序正确读取。但是,生成的代码与我在服务器上生成的代码不匹配。

我尝试了什么


我已经尝试了几件事,希望能找到我的问题。

    我已尝试直接插入两个默认密码: 'thiswasmysecretkeyused'base64.b32encode() 加密版本的秘密: 'ORUGS43XMFZW26LTMVRXEZLUNNSXS5LTMVSA====' 在 Google Authenticator 应用程序中,但是这两个生成的代码都与服务器不同。

    我读到密钥后面的==== 可能会导致它不起作用,所以我也尝试添加一个没有这些的。仍然没有好的结果(它们生成相同的代码)

    我尝试使用不同的算法生成 TOTP 代码,因为万一我使用的算法 (django-otp) 不正确。我使用的不同算法取自 this 答案。两种算法在使用相同密钥时生成相同的代码。

    我检查了系统上的时间。我看到操作系统显示15:03,就像我的智能手机一样。在使用time.time()datetime.datetime.now() 在python 中转储时间后,我看到返回的时间比操作系统时间晚一小时;显示14:03。我尝试在用于代码生成的时间戳中添加3600 秒,但无济于事。

    我尝试了其他几种方法,但记不起它们都是什么。

    我在 Google Authenticator 中查找了接受密钥的代码,并确认它需要一个 base32 字符串。因此,据我所知,我对密钥的编码是正确的。来自代码(EnterKeyActivity.java,第 78 行):

    验证输入字段是否包含有效的 base32 字符串

代码


生成密钥;
def generate_shared_key(self):
    # create hash etc.
    return base64.b32encode(hasher.hexdigest())

生成二维码;

key = authenticator.generate_shared_key()
qrcode = pyqrcode.create('otpauth://totp/someurl.nl?secret=' + key)

生成 TOTP 代码;

def generate_code(self, drift_steps=0, creation_interval=30, digits=6, t0=0):
    code = str(totp(self.generate_shared_key(), creation_interval, timestamp, digits, drift_steps))
    return code.zfill(digits)

如果您需要更多代码,例如 django-otp 实际 totp 生成代码,请告诉我。

错误


没有错误。

预感


我的预感是我在生成密钥或将密钥传递给 Google Authenticator 时一定有问题。因为即使手动将密钥放入 Google Authenticator 也无法生成正确的代码。保存后,Google 身份验证器是否会对密钥执行更多操作,例如添加用户?

我还注意到在我使用的另一个算法中,那里的秘密首先被解码;

key = base64.b32decode(secret, True) 

我的原始密钥(SHA512 哈希)是否错误?我应该还是不应该用base64.b32encode() 对其进行编码?如果我尝试扫描生成的 QR 码而不对哈希进行编码,Google Authenticator 会说它不会将其识别为(有效)密钥。

【问题讨论】:

base64.base32encode() 字符串是正确的,所以也可以发布不匹配的字符串。 我假设您已经尝试按照these examples?中所示的方式实现? @l'L'l 你能详细说明你的意思吗?我知道密钥是正确的(只要它们相同),但我应该向 Google Authenticator 发送一个base64.b32encode() 编码字符串吗? “不匹配的那个”是什么意思? @BobDylan 嗨,鲍勃,不,我没有尝试过这些,但没关系。我尝试的第二种方法只是生成 OTP,应该在服务器端和客户端生成相同的密码,但不会。 【参考方案1】:

好吧,在我翻遍the code of Google Authenticator之后终于发现我做错了什么。

密钥编码

很清楚:Google Authenticator确实期望base32 编码的字符串作为秘密。因此,无论您是手动输入还是通过二维码输入,当您将密码提供给 Google Authenticator 时,都必须确保您的密码是 base32 编码字符串。

来自EnterKeyActivity:

/*
 * Verify that the input field contains a valid base32 string,
 * and meets minimum key requirements.
 */
private boolean validateKeyAndUpdateStatus(boolean submitting) 
    //...

存储

Google Authenticator 将您提供的密钥按原样存储在数据库中。所以这意味着它将您的秘密的base32 字符串直接存储在数据库中。

来自EnterKeyActivity:

private String getEnteredKey() 
    String enteredKey = mKeyEntryField.getText().toString();
    return enteredKey.replace('1', 'I').replace('0', 'O');


protected void onRightButtonPressed() 
    //...
    if (validateKeyAndUpdateStatus(true)) 
        AuthenticatorActivity.saveSecret(this, mAccountName.getText().toString(), getEnteredKey(), null, mode, AccountDb.DEFAULT_HOTP_COUNTER);
        exitWizard();
    
    //...

来自AuthenticatorActivity:

static boolean saveSecret(Context context, String user, String secret, String originalUser, OtpType type, Integer counter) 
    //...
    if (secret != null) 
          AccountDb accountDb = DependencyInjector.getAccountDb();
          accountDb.update(user, secret, originalUser, type, counter);

          //...
    

检索

当 Google Authenticator 从数据库中检索密钥时,它会解码 base32 字符串,以便使用真正的密钥。

来自OtpProvider:

private String computePin(String secret, long otp_state, byte[] challenge) throws OtpSourceException 
    //...

    try 
        Signer signer = AccountDb.getSigningOracle(secret);
        //...
    

来自AccountDb:

static Signer getSigningOracle(String secret) 
    try 
        byte[] keyBytes = decodeKey(secret);
        //...
    


private static byte[] decodeKey(String secret) throws DecodingException 
  return Base32String.decode(secret);

错误

我的错误是,在服务器端,我使用base32 编码的密钥来生成 TOTP 代码,因为我认为 Google Authenticator 也使用了它。事后看来,这当然是非常合乎逻辑的,但我找不到太多关于此的信息。希望这将在未来帮助更多的人。

TL;DR

确保您传递给 Google Authenticator 的密钥/密钥是 base32 编码字符串。确保在服务器端您使用的不是base32 编码字符串,而是解码字符串。在 Python 中,您可以按如下方式对您的密钥/密钥进行编码和解码:

import base64

base64.b32encode(self.key)
base64.b32decode(self.key)

【讨论】:

以上是关于Google 身份验证器代码与服务器生成的代码不匹配的主要内容,如果未能解决你的问题,请参考以下文章

使用 PHP 的 Google 身份验证器

如何使用 JWT for Google firebase 生成身份验证令牌?

java 示例登录Android上的Google登录活动,该活动检索用于服务器端身份验证的授权代码。见htt

来自服务器的 Google 身份验证

通过objective-c从azure表生成身份验证头

PyOTP 生成的代码与 Google Authenticator 生成的代码不匹配