Google Authenticator代码与服务器生成的代码不匹配



背景


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

问题


扫描二维码效果良好,谷歌验证器应用程序可以正确读取二维码。但是,生成的代码与我在服务器上生成的代码不匹配。

我试过什么


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

  1. 我已经尝试直接插入两个默认秘密:1和秘密的CCD_ 2编码版本:Google Authenticator应用程序中的'ORUGS43XMFZW26LTMVRXEZLUNNSXS5LTMVSA====',但这两个程序生成的代码与服务器不同。

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

  3. 我尝试使用不同的算法来生成TOTP代码,因为在不太可能的情况下,我使用的算法(django-otp)是不正确的。我使用的不同算法取自这个答案。当使用相同的密钥时,两种算法都生成了相同的代码。

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

  5. 我试过其他几种东西,但都记不清是什么了。

  6. 我在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生成代码,请告诉我。

错误


没有错误。

Hunches


我的直觉是,我的密钥生成或将密钥传递给Google Authenticator肯定出了问题。因为即使手动将密钥放入Google Authenticator也无法生成正确的代码。保存密钥后,Google Authenticator是否会对密钥做更多的处理,例如添加用户?

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

key = base64.b32decode(secret, True) 

我的原始密钥(SHA512哈希)错了吗?我应该还是不应该用'thiswasmysecretkeyused'0对它进行编码?如果我试图扫描在没有对哈希进行编码的情况下生成的二维码,谷歌验证器会说它无法将其识别为(有效)密钥。

好吧,在仔细研究了Google Authenticator的代码后,我终于发现我做错了什么。

密钥编码

很明显:Google Authenticator确实期望base32编码的字符串作为机密。因此,无论你是手动输入还是通过二维码输入,当你将密码交给谷歌验证器时,你都必须确保你的密码是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)

相关内容

  • 没有找到相关文章

最新更新