2010-07-22 81 views
16

这是为了在数据库中引用一个很好的简短URL,指向一个md5散列。我想的东西转换是这样的:PHP - 从长md5哈希生成一个短的字母数字字符串的好方法是什么?

a7d2cd9e0e09bebb6a520af48205ced1

弄成这个样子:

hW9lM5f27

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

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

编辑 我意识到我最初的计算是完全错误的(感谢的人回答在这里,但我花了一段时间来的线索),并在所有较低扔你不能真正减少字符串长度很大小写字母组合。所以我想我会想要的东西,不直接从十六进制转换为基地62.

+2

随着基64编码您将只能够输入减少到(4/8)/(6/8) - > 4/6〜66%的尺寸(这是假设你处理“丑陋的”base64字符而不添加任何新的)。我可能会考虑一种(二级)查找方法来获得真正的“漂亮”值。 – 2010-07-22 23:27:33

+0

Re“所以我想我会想要不直接从十六进制转换为基数62的东西。” - 如果你想在URL安全字符串中编码16个字节,我的答案(22个字符)可能是最好的。你究竟在努力实现什么? – dkamins 2010-07-23 17:44:34

回答

1

当然,如果我想要一个功能完全满足我的需求,我最好自己做。这是我想出来的。

//takes a string input, int length and optionally a string charset 
//returns a hash 'length' digits long made up of characters a-z,A-Z,0-9 or those specified by charset 
function custom_hash($input, $length, $charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUFWXIZ'){ 
    $output = ''; 
    $input = md5($input); //this gives us a nice random hex string regardless of input 

    do{ 
     foreach (str_split($input,8) as $chunk){ 
      srand(hexdec($chunk)); 
      $output .= substr($charset, rand(0,strlen($charset)), 1); 
     } 
     $input = md5($input); 

    } while(strlen($output) < $length); 

    return substr($output,0,$length); 
} 

这是一个非常通用的随机字符串发生器,但因为结果是由输入字符串和对输入的任何细微变化来确定会产生完全不同的结果它不只是任何旧的随机字符串发生器。你可以用这个做所有事情:

custom_hash('1d34ecc818c4d50e788f0e7a9fd33662', 16); // 9FezqfFBIjbEWOdR 
custom_hash('Bilbo Baggins', 5, 'bcdfghjklmnpqrstvwxyz'); // lv4hb 
custom_hash('', 100, '01'); 
// 1101011010110001100011111110100100101011001011010000101010010011000110000001010100111000100010101101 

任何人都可以看到它的任何问题或任何改善的余地?

+0

我不明白为什么你继续计算输入的hd5 ... $ input = md5($ input); 在DO循环的每次迭代中 – 2010-07-23 08:36:48

+0

因为否则随机数字会在您的输出大于32位时重复。我最初使用str_shuffle,但即使这样也会导致更大规模的重复。 – Moss 2010-07-23 08:44:37

0

这取决于什么a7d2cd9e0e09bebb6a520af48205ced1是。假设你正在讨论一个十六进制数,因为它来自md5,那么你可以运行一个base64_encode。如果你有字符串形式的十六进制,你会想运行hexdec。小心你不会遇到maxint问题。

1

你可以做简单的旧base conversion。哈希以十六进制表示,然后可以创建要表示哈希的大小的字母表。 Base64适用于此目的,但您可能需要编写自己的函数,以便最终编码该值,而不是字符串。

但是,请注意,标准Base64包含您不想放入URL的字符; +,/和填充字符=。当来回转换以获得URL安全的Base64编码(或者如果您编写自己的函数时使用一组安全的字符来开始),您可以用其他字符替换这些字符。

8

下面是考虑一个小功能:

/** 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,在这种情况下,你可以直接使用$ md5_bin_str。

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

P.S.为了输入“a7d2cd9e0e09bebb6a520af48205ced1”(32个字符),该功能将返回“VUDNng4JvrtqUgr0QwXO0Q”(22个字符)。

+0

根据我的计算,9个字符的a-zA-Z0-9应该足以存储md5散列,因此22个字符不如我期望的那么好。我不太了解base64,为什么它会增加尺寸?难道没有更适合实际缩小字符串大小的东西吗? – Moss 2010-07-23 01:32:57

+0

好吧,我的计算结果一定是错的,你需要22个字符来表示哈希,但我无法弄清楚我的数学错在哪里。如果md5散列中的每个字符代表16位,并且有32个字符应该是16 * 32 = 512位(但维基百科称md5是128位)。所以62 * 9 = 558位。它看起来像9位数字应该能够包含一个md5的512位。 - BAH,好吧,我刚刚意识到一个十六进制字符实际上是4位,而不是16位。为什么这让我很困惑...... – Moss 2010-07-23 02:11:18

+0

每个十六进制数字字符= 4位。 32个十六进制字符= 128位= 16个字节。 Base-64仅使用每个输出字节的6位(以保持ASCII安全输出),因此需要4个字节(6 + 6 + 6 + 6)来编码3个字节(8 + 8 + 8)。这就是16个原始字节需要22个编码字节的原因。 Base-64牺牲空间效率来实现更广泛的媒体兼容性。 – dkamins 2010-07-23 17:50:05

1

我建议针对 1-1对应:

随着基64编码您将只能够输入减少到(4/8)/(6/8) - > 4 /大小为6〜66%(这是假设你处理“丑陋”的base64字符而不添加任何新内容)。

我可能会考虑一种(辅助)查找方法来获得真正的“漂亮”值。一旦建立了这种替代方法,选择如何生成该范围内的值 - 例如随机数 - 可以不含源哈希值(因为函数会丢失),可以使用任意的“漂亮”目标集,可能是[a-z] [A-Z] [0-9]。

您可以通过简单地遵循分隔进位方法和查找数组来转换为基数(上面62)。这应该是有趣的小练习。注意:如果您从[0,62^5)中选择随机数,那么您将得到一个将完整打包编码输出(并适合32位整数值)的值。然后,您可以连续多次执行此过程以获得5倍结果值的良好倍数,例如xxxxxyyyyyzzzzzz(其中x,y,z是不同的组,总值在范围内(62^5)^ 3 - > 62^15 - > “巨大的价值”)

编辑,发表评论

因为没有的一一对应可以使真正的短漂亮的东西 - 也许是“小“长度为8个字符 - 使用base62,8个字符最多可以存储218340105584896个值,这可能会超过您的需要。甚至6个字符,其中“仅”允许存储56800235584不同的值! (而且你仍然不能用普通的32位整数存储该数字:-)如果你下降到5个字符,你再次减少空间(不到10亿:916,132,832),但现在你有一些可以符合一个有符号的32位整数(虽然有点浪费)。

数据库应该确保没有重复,尽管此值的索引将随机源“快速分片”(但您可以使用计数器或其他)。一个分布良好的PRNG应该在足够大的范围内有最小的冲突(读取:重试)(假设你保持种子滚动并且不重置它,或者适当重置) - Super 7甚至可以保证在一个周期内没有重复(只有~32k),但正如你所看到的,目标空间仍然是。在最小编码大小的方面,请参见维护1-1关系所需的顶部数学。

分而治之方法只是解释如何让你的源代码到不同的基地 - 也许base62。相同的一般方法可以应用于从“自然”基础(PHP中的base10)到任何基础。

+0

为什么你会建议不要与1-1对应?我不知道你在说什么分而治之法,但这听起来很有趣。 – Moss 2010-07-23 01:36:51

5

这里有两个转换函数用于基本-16至基础-64转换和逆BASE-64至基础-16的任意输入长度:

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

如果需要Base-64 encoding with the URL and filename safe alphabet,可以使用这些函数:

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)); 
} 
+0

非常好。这看起来是进行纯粹的可逆转换的最佳方法。我正在查看PHP手册中的pack/unpack,但我无法理解它。 我决定用我的需要去'有损'压缩方法。 stackoverflow允许两个接受的答案? – Moss 2010-07-23 20:22:33

+0

@Moss:不,你只能接受一个答案。 – Gumbo 2010-07-23 20:44:03

相关问题