PHP - 从长md5哈希生成短字母数字字符串的好方法是什么?

2022-08-30 22:16:34

这是为了有一个很好的短URL,它指的是数据库中的md5哈希。我想转换这样的东西:

a7d2cd9e0e09bebb6a520af48205ced1

变成这样:

hW9lM5f27

这两者都包含大约相同数量的信息。该方法不必是直接和可逆的,但那会很好(更灵活)。至少我想要一个随机生成的字符串,其中十六进制哈希作为种子,这样它就可以重现。我相信有很多可能的答案,我很好奇人们会如何以优雅的方式做到这一点。

哦,这不必与原始哈希值有完美的1:1对应关系,但这将是一个奖励(我想我已经用可逆性标准暗示了这一点)。如果可能的话,我想避免碰撞。

编辑我意识到我最初的计算是完全错误的(感谢这里回答的人,但我花了一段时间才知道),你不能通过把所有的小写字母和大写字母都扔进去来真正减少字符串的长度。所以我想我会想要一些不会直接从十六进制转换为62进制的东西。


答案 1

下面是一个供考虑的小函数:

/** Return 22-char compressed version of 32-char hex string (eg from PHP md5). */
function compress_md5($md5_hash_str) {
    // (we start with 32-char $md5_hash_str eg "a7d2cd9e0e09bebb6a520af48205ced1")
    $md5_bin_str = "";
    foreach (str_split($md5_hash_str, 2) as $byte_str) { // ("a7", "d2", ...)
        $md5_bin_str .= chr(hexdec($byte_str));
    }
    // ($md5_bin_str is now a 16-byte string equivalent to $md5_hash_str)
    $md5_b64_str = base64_encode($md5_bin_str);
    // (now it's a 24-char string version of $md5_hash_str eg "VUDNng4JvrtqUgr0QwXOIg==")
    $md5_b64_str = substr($md5_b64_str, 0, 22);
    // (but we know the last two chars will be ==, so drop them eg "VUDNng4JvrtqUgr0QwXOIg")
    $url_safe_str = str_replace(array("+", "/"), array("-", "_"), $md5_b64_str);
    // (Base64 includes two non-URL safe chars, so we replace them with safe ones)
    return $url_safe_str;
}

基本上,MD5 哈希字符串中有 16 字节的数据。它的长度为 32 个字符,因为每个字节都编码为 2 个十六进制数字(即 00-FF)。因此,我们将它们分解为字节,并构建一个16字节的字符串。但是,由于这不再是人类可读或有效的ASCII,因此我们将其编码回可读字符。但是由于 base-64 导致 ~4/3 的扩展(我们每 8 位输入仅输出 6 位,因此需要 32 位来编码 24 位),因此 16 字节变为 22 字节。但是,由于 base-64 编码通常填充到 4 的长度倍数,因此我们只能采用 24 个字符输出的前 22 个字符(其中最后 2 个字符是填充)。然后,我们将 base-64 编码使用的非 URL 安全字符替换为 URL 安全等效字符。

这是完全可逆的,但这是留给读者的练习。

我认为这是你能做的最好的事情,除非你不关心人类可读/ASCII,在这种情况下,你可以直接使用$md 5_bin_str。

此外,如果您不需要保留所有位,则可以使用此函数的结果的前缀或其他子集。抛出数据显然是缩短事情的最简单方法!(但那是不可逆的)

P.S. 对于您输入的“a7d2cd9e0e09bebb6a520af48205ced1”(32个字符),此函数将返回“VUDNng4JvrtqUgr0QwXO0Q”(22个字符)。


答案 2

以下是用于任意输入长度的 Base-16 到 Base-64 转换和反 Base-64 到 Base-16 转换的两个转换函数:

function base16_to_base64($base16) {
    return base64_encode(pack('H*', $base16));
}
function base64_to_base16($base64) {
    return implode('', unpack('H*', base64_decode($base64)));
}

如果您需要使用 URL 和文件名安全字母的 Base-64 编码,则可以使用以下函数:

function base64_to_base64safe($base64) {
    return strtr($base64, '+/', '-_');
}
function base64safe_to_base64($base64safe) {
    return strtr($base64safe, '-_', '+/');
}

如果您现在希望函数使用 URL 安全字符压缩十六进制 MD5 值,则可以使用以下方法:

function compress_hash($hash) {
    return base64_to_base64safe(rtrim(base16_to_base64($hash), '='));
}

反函数:

function uncompress_hash($hash) {
    return base64_to_base16(base64safe_to_base64($hash));
}

推荐