谷歌身份验证器在苹果设备上,某些密钥无效。

9
我们在一个应用程序中使用了Google Authenticator来实现双因素身份验证。 在QA测试中,出现了一些奇怪的问题。虽然我设法解决了它,但我不确定为什么这个解决方案有效。
对于我们的共享秘钥,我们在用户开始TFA设置时分配了一个GUID。该GUID被Base-32编码,并放入URL中,该URL将转换为QR代码,并由用户使用手机扫描:
 otpauth://totp/myapp_user?secret=g5swmnddhbtggllbgi3dsljumi3tallbmuytgljtg5sdgnbxmy2dgyjwmy======

我们尝试过所有非iOS设备,都能正常运作。但是在iOS设备上,大多数情况下尝试扫描条形码时会出现一个非常奇怪的错误:

Invalid barcode

The barcode '[same as above]' is not a valid authentication token barcode.

它符合Google/RFC 4226的最小秘密要求(128位),经过适当的Base32编码等...为什么会失败?这个消息通常出现在url中存在空格(但实际上并没有)的情况下。
如果我在guid的开头添加一个小种子,一切都正常运行:
otpauth://totp/myapp_user?secret=nfygq33omvzxky3lom3ggmzyha2tgnjnmu4gezbngqzdgyrnhbtdqzrnmeywimrwmjsgknzymi3a

基本上,这是以下两者之间的区别:
 secret = enc.Encode32(Encoding.ASCII.GetBytes("iphonesucks" + Guid.NewGuid().ToString()));  // Works

 secret = enc.Encode(Encoding.ASCII.GetBytes(Guid.NewGuid().ToString())); // Fails

 newAuthUrl = string.Format("otpauth://totp/myapp_user?secret={0}", secret);

我有两个关于为什么这可能起作用的疯狂理论:
  1. ios端口需要超过128位。 我的评论/种子足以将其推动到该限制之上,无论它是什么...除非我实际上给了它更多超过128位,因为它是一个guid-as-string。
  2. 在Base32解码之后,ios应用程序将秘密字符串识别为guid,并对其进行其他处理。
我讨厌修复错误但不知道修复原因。 有人能解释一下吗? 关于此主题的其他阴谋论也欢迎。
5个回答

10

我遇到了与上述相同的问题。原来是Google Authenticator不喜欢iPhone应用中的等号符号,但在Android上没有抱怨。

在我的情况下,我将从8个字符增加到10个字符的字符串长度进行了Base32编码前的处理。这样就删除了字符串末尾的三个 ===。我在网上发现了这个关于为什么在Base32编码后字符串中出现 = 标志的解释:

填充字符(=)在BASE32中没有二进制表示;它会被插入到BASE32文本中作为占位符以保持40位对齐

在你上面的情况中,当你添加盐时也发生了同样的事情。你粘贴的第二个密钥末尾没有 ='s。

希望这有所帮助。


今天你救了我一命,我疯狂地寻找我的Android和iOS客户端之间的差异。谢谢伙计。 - Chester Husk

5

五年后......我们只能让Google身份验证器接受16位数字的密钥。较短或较长的密钥将无法通过检验,出现“无效的条形码。该条形码......不是有效的身份验证令牌条形码。”花费了很多时间进行故障排除,希望这能有所帮助。


这很有帮助,谢谢。但在出现这个错误之前,我能够使用长达18个字符的密钥长度。干杯。 - joshweir

0

重要的不仅是密钥长度

以下是示例URI的格式: otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example

在您的情况下,URI中没有“发行人”或“帐户名称”,建议都是recommended

附注:仅供参考,Google Authenticator也不支持非常长的密钥。


0

对于上面的答案,我无法弄清如何使base32在我的PHP中工作。我有一些代码可以工作,也有一些代码不能工作,它们看起来很随机。

我正在生成一个基于大写md5的前16个字符的密钥。有时它们会工作,但大多数情况下不会。

经过长时间的测试,我发现任何字符串中带有0或1都会失败。其他所有字符串似乎都没问题。也许我错了(欢迎测试您自己的假设),但这对我有效。

$secret['1'] = 'XVQ2UIGO75XRUKJO'; //有效

$secret['1'] = 'XVQ4UIG07SXRUKJO'; //失败

$secret['1'] = 'XVQ2UIG07SXRUKJO'; //失败

$secret['1'] = 'XVQ2UIG07SXRUKJO'; //失败

$secret['1'] = 'ABCDEFGHIJKLMNOR'; //有效

$secret['1'] = '1234567890123456'; //失败

$secret['1'] = '1A3B5C7D9E1F3G5H'; //失败

$secret['1'] = 'AB1DE2GH3JK4MN5R'; //失败

$secret['1'] = 'ABC1EFG2IJK3MNO4'; //失败

$secret['1'] = 'ABCDEFG2IJK3MNO4'; //成功

$secret['1'] = 'ABCD123456789012'; //失败

$secret['1'] = 'ABCDE23456789012'; //失败

$secret['1'] = 'ABCDEF3456789012'; //失败

$secret['1'] = 'ABCDEFG456789012'; //失败

$secret['1'] = 'ABCDEFGH56789012'; //失败

$secret['1'] = 'ABCDEFGHIJBKCLMD'; //成功

$secret['1'] = 'AAAAAAAAAAAAAAAA'; //成功

$secret['1'] = '1111111111111111'; //失败

$secret['1'] = 'A1A1A1A1A1A1A1A1'; //失败

$secret['1'] = 'AAA1AAA1AAA1AAAA'; //失败

$secret['1'] = 'AAAAAAA1AAAAAAAA'; //失败

$secret['1'] = 'AAAA1AAAAAAAAAAA'; //失败

$secret['1'] = 'AAA1AAAAAAAAAAAA'; //失败

$secret['1'] = 'AAAAAAAAAAAAAAAA'; //仍然有效

$secret['1'] = 'AAAAAAA5AAAAAAAA'; //有效

$secret['1'] = 'AAAAAAA1AAAAAAAA'; //失败

$secret['1'] = 'A5A5A5A5A5A5A5A5'; //有效

$secret['1'] = 'A2A2A2A2A2A2A2A2'; //有效

$secret['1'] = 'A0A2A2A2A2A2A2A2'; //失败

$secret['1'] = 'A3A2A2A2A2A2A2A2'; //有效,假设任何一个数字为1或0都会导致失败

$secret['1'] = 'XVQ2UIG17SXRUKJO'; //带1的情况下失败

$secret['1'] = 'XVQ2UIG27SXRUKJO'; //没有1也能运行

$secret['1'] = 'XVQ2UIG07SXRUKJO'; //加了0就失败了

$secret['1'] = 'XVQ2UIG37SXRUKJO'; //加了3就能运行

$secret['1'] = 'XV02UIG37SXRUKJO'; //加了0就失败了


8和9也不起作用。除了0-F的所有其他值都可以使用。 - user2895410

-1

我也有这个问题。

这个链接是有效的。

otpauth://totp/xxx.yyy:usertest009%40xxx.yyy?secret=CC5FCZNWTKNTOVN6&period=30&digits=6&algorithm=SHA1&issuer=xxx.yyy

但是使用不同的密钥将使其无效

otpauth://totp/xxx.yyy:usertest009%40xxx.yyy?secret=PBUPKS3SLJAP9V2T&period=30&digits=6&algorithm=SHA1&issuer=xxx.yyy

我找到了问题所在 密钥必须是base32字符串。 我正在使用NodeJS,因此我使用speakeasy创建了base32的秘密字符串。现在它可以工作了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接