【问题标题】:Generate unique serial from id number从 ID 号生成唯一序列号
【发布时间】:2023-10-01 07:06:01
【问题描述】:

我有一个增量增加 id 的数据库。我需要一个函数将该 id 转换为 0 到 1000 之间的唯一数字。(实际最大值要大得多,但只是为了简单起见。)

1 => 3301, 
2 => 0234, 
3 => 7928,
4 => 9821

生成的号码不能重复。 它不能是增量的。 需要动态生成(而不是创建要读取的统一数字表)

我认为是散列函数,但有可能发生冲突。 随机数也可能有重复。 我需要一个最小的完美哈希函数,但找不到简单的解决方案。

【问题讨论】:

  • 如果您将 0 到 1000 之间的数字转换为 0 到 1000 之间的另一组数字,您将任意将一个数字映射到另一个数字。增量 ID 有什么问题?
  • 这正是我需要做的......从一个集合转换到另一个集合,但我不知道一个简单的函数来完成这个。需要转换 id 的原因是因为我的老板想创建一个网站来通过这个唯一的 id 搜索数据库。他认为,如果人们知道它是增量的,他们可以增量地进行搜索以找到数据库中的每一项。
  • 人们无法找到所有项目有多重要?如果使用某种散列函数,有人可能会发现散列函数,然后无论如何都会增量搜索。为了使这在密码学上真正安全,您要么需要生成随机的东西并存储它,要么使用其他人无法获得的密钥加密 id。
  • 我同意,但这就是他想要的。每个 id 将采用 01-****** 的形式,起初我只是将数字转换为十六进制,例如 01-0002af,但他认为从中数太容易了。它不必非常安全,我猜他认为普通人不会尝试破解哈希。

标签: mysql


【解决方案1】:

由于标准有点模糊(足以愚弄普通人),我不确定该采取哪条路线。以下是一些想法:

  1. 您可以使用Pearson hash。根据*页面:

    给定一个小的特权输入集(例如,编译器的保留字),可以调整排列表,以便这些输入产生不同的哈希值,从而产生所谓的完美哈希函数。

  2. 您可以只使用一个看起来很复杂的一对一数学函数。这样做的缺点是,由于一对一的要求,很难做出不严格增加或严格减少的。如果您执行(id ^ 2) + id * 2 之类的操作,则 id 之间的间隔会发生变化,并且在不知道原始 id 的情况下不会立即看出函数是什么。

  3. 你可以这样做:

    new_id = (old_id << 4) + arbitrary_4bit_hash(old_id);
    

    这将给出唯一的 ID,并且前 4 位只是垃圾不会立即显而易见(尤其是在读取十进制格式的数字时)。与最后一个选项一样,新 ID 的顺序与旧 ID 的顺序相同。不知道会不会有问题。

  4. 您可以通过创建一个充满“随机”数字的查找数组来硬编码所有 ID 转换。

  5. 您可以使用某种哈希函数生成器,例如 gperf

    GNU gperf 是一个完美的散列函数生成器。对于给定的字符串列表,它以 C 或 C++ 代码的形式生成哈希函数和哈希表,用于根据输入字符串查找值。哈希函数是完美的,这意味着哈希表没有冲突,哈希表查找只需要单个字符串比较。

  6. 您可以使用加密安全机制使用密钥对 ID 进行加密。

希望其中一种对您有用。

更新

这是 OP 要求的旋转移位:

function map($number)
{
    // Shift the high bits down to the low end and the low bits
    // down to the high end
    // Also, mask out all but 10 bits. This allows unique mappings
    // from 0-1023 to 0-1023
    $high_bits = 0b0000001111111000 & $number;
    $new_low_bits = $high_bits >> 3;
    $low_bits =  0b0000000000000111 & $number;
    $new_high_bits = $low_bits << 7;
    // Recombine bits
    $new_number = $new_high_bits | $new_low_bits;
    return $new_number;
}

function demap($number)
{
    // Shift the high bits down to the low end and the low bits
    // down to the high end
    $high_bits = 0b0000001110000000 & $number;
    $new_low_bits = $high_bits >> 7;
    $low_bits =  0b0000000001111111 & $number;
    $new_high_bits = $low_bits << 3;
    // Recombine bits
    $new_number = $new_high_bits | $new_low_bits;
    return $new_number;
}

这种方法有其优点和缺点。我能想到的主要缺点(除了安全方面)是,对于较低的 ID,连续数字将是完全相同的(乘法)间隔,直到数字开始环绕。也就是说

map(1) * 2 == map(2)
map(1) * 3 == map(3)

当然会发生这种情况,因为数字越小,所有高位都是 0,所以 map 函数相当于只是移位。这就是为什么我建议对数字的低位而不是高位使用伪随机数据。这将使定期间隔不那么明显。为了帮助缓解这个问题,我编写的函数只移动了前 3 位并旋转了其余的位。通过这样做,对于所有大于 7 的 ID,常规间隔将不那么明显。

【讨论】:

  • 我真的很喜欢选项 2 和 3。其他人创造了太多的字符。我仍然遇到的问题是范围。如果我有 1000 个 id,我需要将每个 id 映射到 [0, 1000] 范围内的唯一序列。选项 2 和 3 都创建更大的数字。我可以只制作一张表格并将 0 到 1000 混为一谈,但我更愿意在我去的时候生成它们。似乎应该有一个简单的功能可以做到这一点。也许将所有位向左推,并将最远的位向左旋转到开头。例如 100101 到 001011。但是我该如何完成呢?
  • @JohnBaker 这取决于。你想用什么语言来做?
  • @JohnBaker 我编辑了我的答案。因为该函数使用二进制运算,所以范围是 0-1024 而不是 0-1000。可以吗?
  • 对不起,其实是0-1023
  • 是的,这很有帮助,我认为这可能是我会选择的选项。我会把你的评论推高,但我是堆栈溢出的新手,所以我不允许你这样做。谢谢大家
【解决方案2】:

好像不一定是数字吧? MD5-Hash 呢?

从...中选择 md5(id+rand(10000))

【讨论】:

  • 但是可能的冲突呢。他提到了这一点。
  • 好的 - 所以离开 rand() - 我不太了解 ist,但 MD5 不应该达到非常高的数字唯一性吗?
  • 可能。如果不明确检查它们,我不会依赖它。