【问题标题】:Making an optimal pseudo random unique code generator for voucher-like behaviour为类似凭证的行为制作最佳的伪随机唯一代码生成器
【发布时间】:2019-09-23 19:39:06
【问题描述】:

我正在为 IT 技术服务开发一个简单的管理系统。该系统有一个网站,客户可以通过输入他们的唯一代码来检查他们的订单的当前状态。当然,这段代码必须是唯一的,并且希望不容易(手动)暴力破解。

我想在“XKF-042”行的某处输出一个易于阅读和写下的输出。问题出现在这些值的生成上:我可以使用纯随机数据并生成两段代码,但这迫使我检查代码是否已经存在,这感觉就像是资源的指数浪费。

一个简单的答案是从任意数字开始计数,比如“ABC-001”,然后加 1,因此不需要检查该值是否已经存在。问题在于暴力破解的容易程度。任何人都可以检查 ABC-XXX 并检查最后一千个连续订单。

数学不是我的强项,但我知道必须有一个更优雅的解决方案来解决这个问题。

我正在考虑为代码的每一侧生成每一个可能的排列并将它们打乱,所以我有一个可以读取的对列表,看起来是随机的,并且可能每移动一次“代码的右侧”列表1000 个代码。

编辑:无法猜测代码并不重要;除了订单信息和成本外,输出不会有任何个人信息。我可以使用 4x4 代码(如“SJDM-4823”)使其“更强大”。

【问题讨论】:

  • 您对生成的代码的长度有限制吗?显然,如果有人必须输入它可能不应该太长。
  • 将问题分解为 a) 生成唯一标识符,b) 生成易于记下/拼写的标识符,c) 生成难以猜测的标识符。每个都可以用一堆方法来解决,这取决于优先级是什么
  • 你使用什么编程语言?
  • 我是用PHP做这个的,除了可用性之外对大小没有真正的限制,但是从100万个代码开始听起来就足够了

标签: php random


【解决方案1】:

好吧,我建议使用 LCG,Linear Congruential Generator。它具有非常好的属性——给定正确的常量集(赫尔-多贝尔定理)输出唯一地覆盖所有 232 空间(或 64 位空间,但据我记得 PHP 只有 32 位整数)。基本上它是从 [0...232) 间隔到另一个 [0...232) 间隔的一对一映射器。它可以有两种使用方式

一,IDnext = LCG(IDprev),类似于典型的随机数生成器。或者只是从线性递增计数器 IDnext = LCG(1, 2, ...) 输入它。您可以将整数转换为 8 个符号 base-16 字符串,应该足够了。

没有PHP代码,有一个python代码

import numpy as np

class LCG:

    UZERO: np.uint32 = np.uint32(0)
    UONE : np.uint32 = np.uint32(1)

    def __init__(self, seed: np.uint32, a: np.uint32, c: np.uint32) -> None:
        self._seed: np.uint32 = np.uint32(seed)
        self._a   : np.uint32 = np.uint32(a)
        self._c   : np.uint32 = np.uint32(c)

    def next(self) -> np.uint32:
        self._seed = self._a * self._seed + self._c
        return self._seed

    def seed(self) -> np.uint32:
        return self._seed

    def set_seed(self, seed: np.uint32) -> np.uint32:
        self._seed = seed

    def skip(self, ns: np.int32) -> None:
        """
        Signed argument - skip forward as well as backward

        The algorithm here to determine the parameters used to skip ahead is
        described in the paper F. Brown, "Random Number Generation with Arbitrary Stride,"
        Trans. Am. Nucl. Soc. (Nov. 1994). This algorithm is able to skip ahead in
        O(log2(N)) operations instead of O(N). It computes parameters
        A and C which can then be used to find x_N = A*x_0 + C mod 2^M.
        """

        nskip: np.uint32 = np.uint32(ns)

        a: np.uint32 = self._a
        c: np.uint32 = self._c

        a_next: np.uint32 = LCG.UONE
        c_next: np.uint32 = LCG.UZERO

        while nskip > LCG.UZERO:
            if (nskip & LCG.UONE) != LCG.UZERO:
                a_next = a_next * a
                c_next = c_next * a + c

            c = (a + LCG.UONE) * c
            a = a * a

            nskip = nskip >> LCG.UONE

        self._seed = a_next * self._seed + c_next


#%%
np.seterr(over='ignore')

a = np.uint32(1664525)
c = np.uint32(1013904223)
seed = np.uint32(1)

rng = LCG(seed, a, c)

print(rng.next())
print(rng.next())
print(rng.next())

【讨论】:

    【解决方案2】:

    我写的一篇文章包含advice for unique random identifiers。从您的问题来看,您似乎面临以下困境:生成随机唯一标识符——

    • 足够长,难以猜测,但是
    • 足够短,便于最终用户输入。

    那篇文章中的建议解释了如何生成唯一的随机 ID(128 位或更长,在 PHP 中使用 random_bytesbin2hex 之类的加密 RNG)。但出于您的目的,生成的 ID 可能太长而不适合。有一些方法可以处理这么长的 ID,包括——

    1. 将 ID 划分为可记忆的块(例如:“374528294473”变为“374-538-294-473”),
    2. 将 ID 转换为一系列容易记住的单词(如比特币的 BIP39),或
    3. 在 ID 末尾添加一个所谓的“校验和数字”以防止输入错误。

    在决定使用比该文章中建议的更短的 ID 之前,您应该尝试 (1) 或 (2)。

    此外,您的应用程序通常必须根据已生成的 ID 数据库检查 ID 的唯一性;但是,这种唯一性检查可能会或可能不会成为您的应用程序的性能瓶颈,唯一的方法是尝试并查看。或者,您可以将 ID 表的记录号(对于每条记录应该是唯一的)包含在 ID 本身中。


    如果订单 ID 是唯一授予访问该订单信息的权限,则可能存在严重的安全问题。理想情况下,应该有其他形式的授权,例如只允许登录用户或某些登录用户访问与订单 ID 关联的订单信息。另见this question

    【讨论】:

    • 系统没有严重的瓶颈或“需要”在性能上完全完美。这篇文章虽然读起来很有趣,但似乎进入了 rng 的不同用法。切碎哈希仍然可能会产生碰撞,所以它并不适合。 BIP39 听起来很有趣,因为它更容易记住,但我不确定实现。系统不需要很复杂,所以如果我必须检查某些东西,我只需检查 ID。
    • “切碎哈希”是什么意思?当我在上面写“将ID分成块”时,我的意思是将ID分成可记忆的块;例如:“374528294473”变为“374-538-294-473”以简化记住该 ID 的任务。以这种方式划分 ID 并不一定会减少 ID 冲突。
    • 是这样想的,但这让我们回到了问题的可用性部分,因为它的代码旨在提供一些信息,如果用户决定使用它(以及更大的存储空间)对于更大的代码)。这是一个棘手的问题,这就是为什么我对找到一个干净的解决方案有点兴趣。
    • 好吧,还有最后一件事:授权。更具体地说,订单 ID 不应该是唯一授予访问该订单信息的权限。因此,您应该只允许授权用户输入任何特定的订单 ID。这将使您可以使用较短的 ID,同时大大减少难以猜测的 ID 问题。
    • 考虑使用字母数字 ID。只有 10 位数字,但有 26 个数字。如果 ID 的每个字符有 36 种可能性(10 位数字加上 26 个数字),那么对于给定的 ID 长度,您可以获得更大的数字空间。
    猜你喜欢
    • 2014-03-12
    • 2011-08-19
    • 1970-01-01
    • 2018-06-25
    • 2019-03-25
    • 2011-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多