2010-07-22 80 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

相關問題