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

标签 python django two-factor-authentication google-authenticator one-time-password

背景


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

问题


二维码扫描工作正常,谷歌身份验证器应用程序可以正确读取它。但是,生成的代码与我在服务器上生成的代码不匹配。

我尝试了什么


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

  1. 我试过直接插入两个默认密码: 'thiswasmysecretkeyused' 和 secret 的 base64.b32encode() 编码版本: Google Authenticator 应用中的 'ORUGS43XMFZW26LTMVRXEZLUNNSXS5LTMVSA====',但这些生成的代码都与服务器不同。

  2. 我读到 key 的尾部 ==== 可能会导致它无法正常工作,因此我也尝试添加一个没有这些的 key 。仍然没有好的结果(他们生成相同的代码)

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

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

  5. 我尝试过其他几种方法,但不太记得它们是什么。

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

    Verify that the input field contains a valid base32 string

代码


生成 key ;

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

错误


没有错误。

预感


我的直觉是我一定在 key 生成或将 key 传递给 Google Authenticator 的某个地方出错了。因为即使手动将 key 放入 Google Authenticator 也无法生成正确的代码。保存 key 后,Google 身份验证器是否会对 key 执行更多操作,例如添加用户?

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

key = base64.b32decode(secret, True) 

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

最佳答案

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

按键编码

很明显:Google 身份验证器确实期望将 base32 编码的字符串作为 secret 。因此,无论您是手动输入还是通过二维码输入,当您将其提供给 Google 身份验证器时,都必须确保您的密码是 base32 编码的字符串。

来自 EnterKeyActivity :

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

存储

Google Authenticator 将您提供的 key 按原样存储在数据库中。所以这意味着它将您的 secret 的 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 身份验证器从数据库中检索 secret 时,它会解码 base32 字符串,以便使用真正的 secret 。

来自 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 编码的 key 来生成 TOTP 代码,因为我认为 Google Authenticator 也使用它。事后看来,这当然是非常合乎逻辑的,但我找不到太多关于此的信息。希望这会在未来帮助更多人。

长话短说

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

import base64

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

关于python - Google 身份验证器代码与服务器生成的代码不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34001567/

相关文章:

python - 编辑 django 登录表单

python - 如何在给定点之间添加随机点?

Python,合并多级字典

django - 用于为初学者编写 Twitter 克隆代码的数据库

python - Django 模板 : Translate include with variable

asp.net-mvc-5 - PasswordSignInAsync 返回成功而不是 RequiresVerification

python - 枕头相当于 GIMP 的亮度对比度对话?

python - 使用 h5py 将外部原始文件链接到 hdf5 文件

python - 将 Django 站点从 http 升级到 https 后,我不断收到 `Invalid HTTP_HOST header` 错误电子邮件

c# - "Google Two Factor Authenticator"accountSecretKey 是否应该存储到我的数据库中?