背景
我目前正在开发一个双因素身份验证系统,用户可以使用智能手机进行身份验证 . 在用户可以使用他们的设备之前,他们需要先验证它 . 为此,他们需要扫描我给他们的QR码并输入随后显示的代码 .
问题
扫描QR码工作正常,Google身份验证器应用程序可以正确读取 . 但是,生成的代码不会在服务器上生成 .
我尝试了什么
为了找到问题,我尝试了几件事 .
-
我尝试在Google身份验证器应用程序中直接插入默认密码:
'thiswasmysecretkeyused'
和秘密的base64.b32encode()
编码版本:'ORUGS43XMFZW26LTMVRXEZLUNNSXS5LTMVSA===='
,但这两个代码都生成与服务器不同的代码 . -
我看到密钥中的尾随
====
可能导致它不起作用,所以我尝试添加一个没有那些 . 仍然没有好的结果(他们生成相同的代码) -
我尝试使用不同的算法来生成TOTP代码,因为我使用的算法(django-otp)不太可能 . 我使用的不同算法取自this答案 . 当使用相同的密钥时,两种算法都生成相同的代码 .
-
我检查了我的系统上的时间 . 我看到操作系统显示
15:03
就像我的智能手机一样 . 在使用time.time()
和datetime.datetime.now()
在python中转储时间之后,我看到返回的时间比操作系统时间晚了一个小时;显示14:03
. 我尝试将3600
秒添加到用于代码生成的时间戳中,但无济于事 . -
我已经尝试过其他一些东西,但不能完全回想起它们是什么 .
-
我've looked up the code that accepts keys in Google Authenticator and have verified that it is expecting a base32 string. So my encoding of the key is correct, as far as I'我知道 . 从代码(EnterKeyActivity.java,第78行):
验证输入字段是否包含有效的base32字符串
代码
生成密钥;
def generate_shared_key(self):
# create hash etc.
return base64.b32encode(hasher.hexdigest())
生成QR码;
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 actual totp生成代码,请告诉我 .
错误
没有错误 .
Hunches
我的预感是我必须在密钥生成或将密钥传递给Google身份验证器的某个地方出错 . 因为即使手动将密钥放入Google身份验证器也无法生成正确的代码 . Google身份验证器在保存密钥后是否会使用密钥执行更多操作,例如添加用户?
我还注意到在另一个算法中我使用的秘密首先被解码;
key = base64.b32decode(secret, True)
我的原始密钥(SHA512哈希)是错误的吗?我应该或不应该用 base64.b32encode()
编码吗?如果我尝试扫描生成的QR代码而不对哈希进行编码,Google身份验证器表示它不会将其识别为(有效)密钥 .
1 回答
好吧,经过挖掘the code of Google Authenticator我终于找到了我做错了什么 .
密钥编码
就这么明确:Google身份验证器确实希望将
base32
编码的字符串作为秘密 . 因此,无论您是手动输入还是通过QR码输入,都必须确保在将其提供给Google身份验证器时,您的密码是一个base32
编码的字符串 .从EnterKeyActivity:
存储
Google身份验证器会按原样存储您在数据库中提供的密钥 . 所以这意味着它将您秘密的
base32
字符串直接存储在数据库中 .来自EnterKeyActivity:
来自AuthenticatorActivity:
检索
当Google身份验证器从数据库中检索秘密时,它会解码
base32
字符串,以便它可以使用真正的秘密 .从OtpProvider:
来自AccountDb:
错误
我的错误是,在服务器端,我使用
base32
编码密钥生成TOTP代码,因为我认为Google身份验证器也使用了它 . 事后看来,它找到了太多关于此的信息 . 希望这将有助于将来更多的人 .TL; DR
确保您传递给Google身份验证器的密码/密钥是
base32
编码的字符串 . 确保在服务器端您没有使用base32
编码的字符串,而是使用已解码的字符串 . 在Python中,您可以对密码/密钥进行编码和解码,如下所示: