【问题标题】:Short URL with unique but random generated string?具有唯一但随机生成的字符串的短 URL?
【发布时间】:2020-08-11 22:14:53
【问题描述】:

所以我一直在尝试构建一个更短的 URL,但我无法生成唯一但 random 的字符串。我到处寻找解决方案,但找不到解决方案,因此将其发布在这里。

我有一个表,其中我根据插入的记录自动生成顺序主键 (ID)。现在我使用那个 ID 并在它上面运行一个双射函数,它会变成

0  → a
1  → b
...
25 → z
...
52 → 0
61 → 9

现在的问题是生成的字符串不是随机的。例如:

63 --> b1
64 --> b2
...
1836 --> bpa
1836 --> bpb

这很容易猜到。我什至尝试将 ID 编码为 Base64,但生成的字符串再次可以猜测,如果我使用 GUID 并将其编码为 Base64,则生成的字符串非常大。最大字符串应为 7,8 个字符 - 理想情况下为 3,4 个字符。

我想知道 bit.ly 是如何做到的?他们生成的短 URL 始终是唯一且随机的。

【问题讨论】:

  • 我明白了,我正在查看 CRC32,它会生成 8 个字符串。如果没有更好的解决方案,这是否足够好?由于每个 ID 都是唯一的,CRC32 编码的字符串也将是唯一的。对吗?
  • 不,CRC 编码的字符串不会是唯一的。您正在查看的 CRC 可能会生成一个 32 位数字,显示为 8 个十六进制数字。所以有 2^32 个可能的 CRC 值。如果你有多个可能的 ID,就会发生冲突。

标签: algorithm url base64 short-url bijection


【解决方案1】:

您所做的是生成顺序键。第一个 URL 是 1,下一个是 2,依此类推。但是您通过将每个键唯一地映射到不同的数字来混淆这些键。所以 1 可能会变成 76839427,而 2 可能会变成 9935。然后你对数字进行 base64 编码。

好消息是您只需要跟踪下一个序列号即可。该过程是可逆的,因此您可以将 9935 变回 2。

我在Efficient algorithm for generating unique (non-repeating) random numbers中给出一个映射的例子。

另一种可能性是使用Linear Feedback Shift Register 长句号。您可以创建一个周期为 2^64 的周期。保证在您生成 2^64 个数字之前不会重复。

请注意,这些都不是真正的“随机”。它们是混淆的方法,但只要付出足够的努力,就会有人破解算法。但是,他们也可以破解伪随机数生成器。

【讨论】:

    【解决方案2】:

    将顺序整数转换为非顺序的 4 字符标记相当容易。如果您使用可逆算法,那么您还可以轻松地将这些标记转换回可用于从数据库中检索 URL 的连续整数。

    注意:如果您打算开放一个公共 URL 缩短服务,那么只有 4 个字母数字字符的令牌可能会很快用完。但对于个人或公司网站,它们应该绰绰有余。下面描述的方法也适用于更长的令牌,但如果您真的需要一个可以存储数十亿(或数万亿)个 URL 的系统,那么您需要更仔细地考虑如何组织所有这些数据。

    linear congruential generator 是一种混淆数字的好方法。如果您使用 0 到 624−1 的范围,那么显然您的模数 m 将为 624。由于 m 的质因数是 2 和 31,因此乘数 a 必须比 124 的倍数大一 (as explained here)。 c 的值可以是与 m 互质的任何非零值。例如:

    function lcg($n) {
        # (10345073 - 1) % 124 == 0
        $m = 14776336; # = 62**4
        $a = 10345073;
        $c = 8912423;
        $n = ($n * $a + $c) % $m;
        return $n;
    }
    

    反函数非常相似。它使用modular multiplicative inverse(mod m)代替a,使用m代替c >−c:

    function lcg_inv($n) {
        # (10345073 * 5661345) % (62**4) == 1
        $m = 14776336; # = 62**4
        $a_ = 5661345;
        $c_ = 5863913; # = $m-8912423
        $n = (($n + $c_) * $a_) % $m;
        return $n;
    }
    

    由于 LCG 很容易根据几个输出值进行预测,因此您可以通过随机化用于以 62 为基数表示这些数字的符号顺序来添加另一层混淆(例如,W3qVL... 而不是 abcde...)

    function int_2_token($n) {
        $alf = 'W3qVLpEKDxn8vzG0SQPfIX2yO51JsHBYCRbouTatZ4hMdlmF67UcNiAgwke9jr';
        $tok = '';
        if ($n < 0 || $n >= 62**4) return ''; # Value out of range
        $n = lcg($n);
        for ($i=0; $i<4; $i++) {
            $r = $n % 62;
            $tok .= $alf[$r];
            $n = ($n - $r) / 62;
        }
        return $tok;
    }
    
    function token_2_int($tok) {
        $t = [ '0'=>15, '1'=>26, '2'=>22, '3'=>1,  '4'=>41, '5'=>25, '6'=>48, '7'=>49,
               '8'=>11, '9'=>59, 'A'=>54, 'B'=>30, 'C'=>32, 'D'=>8,  'E'=>6,  'F'=>47,
               'G'=>14, 'H'=>29, 'I'=>20, 'J'=>27, 'K'=>7,  'L'=>4,  'M'=>43, 'N'=>52,
               'O'=>24, 'P'=>18, 'Q'=>17, 'R'=>33, 'S'=>16, 'T'=>37, 'U'=>50, 'V'=>3, 
               'W'=>0,  'X'=>21, 'Y'=>31, 'Z'=>40, 'a'=>38, 'b'=>34, 'c'=>51, 'd'=>44,
               'e'=>58, 'f'=>19, 'g'=>55, 'h'=>42, 'i'=>53, 'j'=>60, 'k'=>57, 'l'=>45,
               'm'=>46, 'n'=>10, 'o'=>35, 'p'=>5,  'q'=>2,  'r'=>61, 's'=>28, 't'=>39,
               'u'=>36, 'v'=>12, 'w'=>56, 'x'=>9,  'y'=>23, 'z'=>13 ];
        $n = 0;
        if (!preg_match('/^[a-z0-9]{4}$/i', $tok)) return -1; # Invalid token
        for ($i=3; $i>=0; --$i) {
            $n = $n * 62 + $t[$tok[$i]];
        }
        return lcg_inv($n);
    }
    

    因此,当您获得一个要缩短的新 URL 时,请将其插入到您的数据库中,并带有一个自动递增的 ID 值,然后将此 ID 值传递给 int_2_token() 以获得一个四字符的令牌以在缩短的 URL 中使用。当请求此缩短的 URL 时,将令牌传递给 token_2_int() 以恢复此 ID,以便您可以获取原始 URL。


    注意:不要忘记所有四字符标记的集合包括所有four-letter words 的整个集合。您可能需要确保您的 URL 缩短器不会输出任何粗俗或令人反感的内容。

    【讨论】:

      猜你喜欢
      • 2012-11-21
      • 2010-10-18
      • 1970-01-01
      • 1970-01-01
      • 2016-07-05
      • 2016-04-19
      • 2011-08-18
      • 2017-02-21
      相关资源
      最近更新 更多