PHP - 什么是生产从长MD5哈希短的字母数字字符串的好办法?

问题描述 投票:16回答:6

这对于具有指的是MD5哈希数据库中的一个漂亮的短网址的目的。我想的东西转换是这样的:

a7d2cd9e0e09bebb6a520af48205ced1

弄成这个样子:

Houselmkhfouh

这些都含有大约相同数量的信息。该方法并不一定是直接的和可逆的,但是这将是很好的(更灵活)。在至少我想用十六进制散列作为种子随机生成的字符串,所以它是可重复的。我敢肯定有很多可能的答案,我很好奇,看看人们会怎么做在一个优雅的方式。

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

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

php random base
6个回答
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,我们基于64位编码回可读的字符。但是,由于碱-64导致〜4/3膨胀(我们只输出每个输入的8位的6位,因此需要32位来编码24位),16个字节成为22个字节。但由于基64编码通常垫4的长度的倍数,我们可以采取仅24字输出(其中最后2个是填充)的第一22个字符。然后,我们使用替换通过碱基64编码与网址安全当量非URL安全字符。

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

我觉得这是你能做的最好的,除非你不关心人类可读/ ASCII,在这种情况下,你可以使用$ md5_bin_str直接。

并且还可以使用结果的前缀或其它亚从这个功能,如果你不需要保存所有的位。扔出去的数据显然是缩短东西最简单的办法! (但是,那么它的不可逆的)

附:您的“a7d2cd9e0e09bebb6a520af48205ced1”(32个字符)的输入,此函数将返回“VUDNng4JvrtqUgr0QwXO0Q”(22个字符)。


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

1
投票

你可以只是做普通的老base conversion。哈希是十六进制表示的,然后你就可以创建你想表达的散列大小的字母。 Base64可以很好地用于此目的,虽然你可能会想编写自己的功能,所以你最终编码值,而不是字符串。

但是请注意,该标准的Base64包含您不想放在一个网址字符; +,/和填充字符=。来回转换时获得一个网址安全Base64编码(或使用安全的字符集与开始,如果你写你自己的功能),可随时更换别的东西这些字符。


1
投票

我会建议对一个1-1对应:

与base64编码您将仅能够减少输入到(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个字符,再次减少空间(只以下的十亿:916132832),但现在你有什么可适合在一个符号的32位整数(尽管它是有些浪费)。

该数据库应确保没有重复,尽管这个值的指标将是“快速碎片”与随机源(但你可以使用计数器或诸如此类的东西)。一个良好的分布式PRNG应该有最小的冲突:在一个足够大的范围(读重试)(假设你把种子滚动,不重置,或者适当地重置) - 超级7可以在一个周期中甚至保证没有重复(仅〜32K的),但你可以在上面看到,目标空间仍然很大。见数学在最小编码量的方面是什么维持1-1之间的关系需要顶层。

分而进位的方法只是说明如何让你的源数为不同的基地 - 也许base62。相同的一般方法可应用于从“天然”碱基(base10在PHP)到任何碱去。


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 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUFWXIZ0123456789'){
    $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, '0123456789bcdfghjklmnpqrstvwxyz'); // lv4hb
custom_hash('', 100, '01'); 
// 1101011010110001100011111110100100101011001011010000101010010011000110000001010100111000100010101101

任何人看到它或任何改进的余地什么问题?


0
投票

这取决于a7d2cd9e0e09bebb6a520af48205ced1是什么。假设你正在谈论的是一个十六进制数字,因为它是从md5来了,你可以只运行一个base64_encode。如果您有以字符串形式十六进制,你想运行hexdec。要小心,你不要碰到MAXINT问题虽然。

© www.soinside.com 2019 - 2024. All rights reserved.