28

我正在开发一个应用程序,用户必须拨打电话并使用手机键盘输入验证码。

我希望能够检测他们输入的数字是否正确。电话系统无法访问有效号码列表,但它会根据算法(如信用卡号码)验证号码。

以下是一些要求:

  • 输入有效的随机码一定很困难
  • 如果我打错字(数字换位,错误数字),一定很难有一个有效的代码
  • 我必须有合理数量的可能组合(比如说 1M)
  • 代码必须尽可能短,以避免用户出错

鉴于这些要求,您将如何生成这样的数字?

编辑 :

@Haaked:代码必须是数字,因为用户是用手机输入的。

@matt b:第一步,代码显示在网页上,第二步是调用并输入代码。我不知道用户的电话号码。

跟进:我发现了几种算法来检查数字的有效性(请参阅这个有趣的 Google 代码项目:checkDigits)。

4

9 回答 9

33

经过一番研究,我想我会使用ISO 7064 Mod 97,10公式。它似乎非常可靠,因为它用于验证 IBAN(国际银行帐号)。

公式很简单:

  1. 取一个号码:123456
  2. 应用以下公式获得 2 位校验和: mod(98 - mod(number * 100, 97), 97) => 76
  3. concat 编号和校验和得到代码 => 12345676
  4. 要验证代码,请验证mod(code, 97) == 1

测试 :

  • mod(12345676, 97) = 1 => 好
  • mod(21345676, 97) = 50=> 糟糕!
  • mod(12345678, 97) = 10=> 糟糕!

显然,该算法捕获了大部分错误。

另一个有趣的选择是Verhoeff 算法。它只有一个校验位,实现起来比较困难(与上面的简单公式相比)。

于 2008-09-05T18:30:26.107 回答
4

对于 1M 组合,您需要 6 位数字。为了确保没有任何意外有效的代码,我建议 9 位数字,随机代码有 1/1000 的可能性。我还建议使用另一个数字(总共 10 个)来执行完整性检查。就分布模式而言,随机就足够了,校验位将确保单个错误不会导致正确的代码。

编辑:显然我没有完全阅读您的请求。使用信用卡号,您可以对其进行哈希处理(MD5 或 SHA1 或类似的东西)。然后,您在适当的位置截断(例如 9 个字符)并转换为以 10 为基数。然后添加校验位,这应该或多或少适合您的目的。

于 2008-09-05T16:33:10.367 回答
2

你想分割你的代码。其中一部分应该是其余代码的 16 位 CRC。

如果您想要的只是一个验证号,那么只需使用一个序列号(假设您有一个生成点)。这样你就知道你没有得到重复。

然后在序列前面加上该序列号的 CRC-16 和一些私钥。您可以将任何东西用作私钥,只要您将其保密。让它变得更大,至少是一个GUID,但它可能是Gutenberg 项目中战争与和平的文本。只需要保持秘密和恒定。拥有私钥可以防止人们伪造密钥,但使用 16 位 CR 更容易破解。

要验证您只需将数字分成两部分,然后获取序列号和私钥的 CRC-16。

如果您想更加模糊顺序部分,则将 CRC 分成两部分。将 3 个数字放在序列的前面,将 2 个数字放在序列的后面(零填充,因此 CRC 的长度是一致的)。

此方法也允许您从较小的键开始。前 10 个键将是 6 位数字。

于 2008-09-05T18:34:17.487 回答
1

必须只有数字吗?您可以创建一个介于 1 和 1M 之间的随机数(我建议甚至更高),然后Base32 对其进行编码。您需要做的下一件事是散列该值(使用秘密盐值)并 base32 对散列进行编码。然后将两个字符串附加在一起,可能用破折号分隔。

这样,您可以通过算法验证传入的代码。您只需获取代码的左侧,使用您的秘密盐对其进行哈希处理,然后将该值与代码的右侧进行比较。

于 2008-09-05T16:44:19.717 回答
0
  • 我必须有合理数量的可能组合(比如说 1M)
  • 代码必须尽可能短,以避免用户出错

好吧,如果您希望它具有至少一百万个组合,那么您至少需要六位数字。够短吗?

于 2008-09-05T16:33:28.540 回答
0

当您创建验证码时,您是否可以访问呼叫者的电话号码?

如果是这样,我会使用呼叫者的电话号码并通过某种散列函数运行它,以便您可以保证您在步骤 1 中提供给呼叫者的验证码与他们在步骤 2 中输入的相同(以确保他们没有使用朋友的验证码,或者他们只是做了一个非常幸运的猜测)。

关于散列,我不确定是否可以取一个 10 位数字并得出一个小于 10 位的散列结果(我想你必须忍受一定数量的冲突)但我认为这将有助于确保用户是他们所说的人。

当然,如果步骤 1 中使用的电话号码与他们在步骤 2 中拨打的电话号码不同,这将不起作用。

于 2008-09-05T16:42:33.790 回答
0

假设您已经知道如何检测用户按下了哪个键,那么这应该很容易做到。在安全世界中,存在“一次性”密码的概念。这有时被称为“一次性密码”。通常,这些仅限于(易于键入的)ASCII 值。因此,[a-zA-z0-9] 和一堆易于输入的符号。比如逗号、句号、分号和括号。但是,在您的情况下,您可能希望将范围限制为 [0-9] 并可能包括 * 和 #。

我无法充分解释这些一次性代码是如何生成(或工作)的所有技术细节。它背后有一些中间数学,我会在没有自己审查的情况下将其杀死。只要说您使用一种算法来生成一次性密码流就足够了。不管你知道多少前面的代码,后面的代码应该是不可能猜到的!在您的情况下,您只需将列表中的每个密码用作用户的随机代码。

我不会自己解释实现的细节,而是将您引导到一篇 9 页的文章,您可以自己阅读:https ://www.grc.com/ppp.htm

于 2008-09-05T16:50:46.673 回答
0

听起来您有一个不言而喻的要求,即必须通过算法快速确定代码是否有效。这将排除您简单地分发一次性垫号码列表。

过去人们有几种方法可以做到这一点。

  1. 制作公钥和私钥。使用私钥对数字 0-999,999 进行编码,然后分发结果。您需要输入一些随机数以使结果得到更长的版本,并且您必须将结果从基数 64 转换为基数 10。当您输入数字时,将其转换回 base64,应用私钥,看看有趣的数字是否低于 1,000,000(丢弃随机数)。
  2. 使用可逆哈希函数
  3. 使用以特定值播种的 PRN 中的前一百万个数字。“检查”功能可以得到种子,并且知道接下来的一百万个值是好的。它可以每次生成它们并在收到代码时一一检查,或者在程序启动时将它们全部存储在一个表中,排序,然后使用二进制搜索(最大比较),因为一百万个整数并不是一个整数的空间。

还有很多其他选项,但这些选项很常见且易于实现。

-亚当

于 2008-09-05T16:51:19.293 回答
0

您链接到检查数字项目,并且使用“编码”功能似乎是一个很好的解决方案。它说:

如果将“坏”数据(例如非数字)传递给它,encode 可能会抛出异常,而 verify 仅返回 true 或 false。这里的想法是编码通常从“受信任的”内部来源(例如数据库密钥)获取数据,因此应该很常见,事实上,传入不良数据是例外的。

因此,听起来您可以向编码函数传递一个数据库密钥(例如 5 位数字),然后您可以得到一个符合您要求的数字。

于 2008-09-05T17:13:13.233 回答