2009-08-19 14 views
17

python中可用的最短哈希(可用於文件名形式,如十六進制摘要)是什麼?我的應用程序想爲某些對象保存緩存文件。這些對象必須具有唯一的repr(),以便它們用於「種子」文件名。我想爲每個對象生成一個可能唯一的文件名(不是那麼多)。它們不應該相互碰撞,但是如果它們做了我的應用程序,那麼這個對象就會缺少緩存(並且必須重新索引該對象的數據,這對應用程序來說是一個小的代價)。Python中用於命名緩存文件的最短哈希

因此,如果發生一次衝突,我們會丟失一個緩存文件,但這是緩存所收集的所有對象的節省,使得應用程序啓動速度更快,所以沒關係。

現在我實際上使用abs(hash(repr(obj)));沒錯,字符串hash!還沒有發現任何碰撞,但我想有一個更好的散列函數。 hashlib.md5在Python庫中可用,但如果放入文件名中,十六進制文件非常長。替代方案,具有合理的碰撞阻力?

編輯: 用例如下: 數據加載器獲取數據攜帶對象的新實例。獨特的類型有獨特的repr。因此如果存在hash(repr(obj))的緩存文件,則取消該緩存文件的替換,並將未替代對象替換爲obj。如果發生衝突並且緩存是錯誤匹配,我會注意到。因此,如果我們沒有緩存或者有錯誤匹配,我會改爲初始化obj(重新加載它的數據)。

結論(?)

str散列蟒蛇可能是不夠好,我只是擔心它的抗碰撞性。但是如果我可以用它來散列2**16對象,它將會不夠好。

我發現瞭如何把一個十六進制哈希(從任何散列源)和使用Base64壓縮存儲它:

# 'h' is a string of hex digits 
bytes = "".join(chr(int(h[i:i+2], 16)) for i in xrange(0, len(h), 2)) 
hashstr = base64.urlsafe_b64encode(bytes).rstrip("=") 
+1

爲什麼你關心文件名的長度?這根本不重要,除非你使用的是愚蠢的文件系統 – 2009-08-19 22:59:42

+0

這很醜陋。所有程序員都希望用更多的方式來表達,而在這裏我知道我可以,一個完整的加密散列是矯枉過正的。 – u0b34a0f6ae 2009-08-19 23:14:08

+0

在結束的例子中,對於python hashlib hash,當然可以使用bytes =(..)。digest()。 – u0b34a0f6ae 2009-08-20 15:50:36

回答

35

birthday paradox適用:給出了良好的散列函數,在碰撞發生前散列的預期數量爲約SQRT(N),其中N是不同值的數目,該散列函數可以採取。 (我指出的維基百科條目給出了確切的公式)。因此,例如,如果您要使用不超過32位的值,那麼對於大約64K個對象(即2**16對象 - 散列函數可以使用的不同值的平方根),您的碰撞擔心是嚴重的。你期望有多少物體,數量級?

既然你提到碰撞是一個小麻煩,我建議你的目標是哈希長度大約是你將擁有的對象數量的平方,或者少一點,但不會少得多。

你想要創建一個文件名 - 是在Unix上典型的區分大小寫的文件系統嗎?還是你也不得不滿足不區分大小寫的系統呢?這很重要,因爲你的目標是短文件名,但是你可以使用每個字符的位數來表示你的散列作爲文件名,在大小寫敏感或不敏感的系統上會發生顯着變化。

在區分大小寫的系統,可以使用標準庫的base64模塊(我建議編碼的「urlsafe」版本,即this功能,避免了「/」字符,可能出現在平原Base64是重要的在Unix文件名中)。這給你每個字符6個可用位,比16位的4位/字符好得多。

即使在不區分大小寫的系統上,仍然可以比hex更好 - 使用base64.b32encode並獲得每個字符5位。

這些函數獲取並返回字符串;如果您選擇的散列函數生成數字,請使用struct模塊將數字轉換爲字符串。

如果你確實有幾萬個對象,我認爲你可以使用內建散列(32位,所以6-7個字符,取決於你選擇的編碼)。對於一百萬個物體,你需要40位左右(7或8個字符) - 你可以將sha256摺疊(xor,不要截斷;-)一個合理的位數,比如說128位左右,並在編碼之前使用%運算符將其進一步剪切至所需的長度。

+1

選擇散列長度的好規則 – u0b34a0f6ae 2009-08-20 11:09:51

3

我敢肯定,有Python中的CRC32實現,但可能是太短(8位數字)。好的,這很快。

發現,binascii.crc32

+1

這是非常快的,這是很好的。但看到它不推薦作爲散列函數,也許字符串的散列()是一樣好? – u0b34a0f6ae 2009-08-19 23:05:33

+0

不推薦CRC作爲散列,理由是它會產生衝突,並且**有目的**相對容易。這使得它不安全,例如用於散列密碼。但它*是一個散列函數,它只是生成一個非常短的散列。這意味着更多潛在的碰撞。雖然它是快速和小型的,但它的正常應用是完整性檢查。如果2^32選項是足夠的,那麼CRC32就可以了(或者顯然Python中的hash()函數也會生成2^32。不知道這一點,我沒有真正使用Python) – 2009-08-19 23:16:53

1

如果你有一個碰撞,你怎麼告訴它實際上發生了什麼?

如果我是你,我會使用hashlib到sha1()repr(),然後只是得到一個有限的子串(例如前16個字符)。

除非你在談論大量的這些對象,否則我會建議你只使用完整散列。那麼碰撞的機會是如此,所以,如此之小,以至於你永遠不會看到它發生(可能)。

此外,如果你正在處理很多文件,我猜你應該調整你的緩存技術來適應它。

+0

我解開了緩存,注意什麼時候出現錯誤,所以碰撞只是兩個碰撞對象的滋擾,一個在應用程序啓動時總是沒有緩存。 但這是一個非常好的建議,因爲sha1是不會相互衝突的哈希函數的類型,並且哈希中的切片是我沒有想到的。 – u0b34a0f6ae 2009-08-19 23:07:30

+0

實際上,由於各種數學原因,使用哈希的子串會產生比僅使用更短的哈希函數更多的碰撞。作爲協議的一部分,請參閱例如實時生成部分SHA1衝突的協議。 – 2009-08-19 23:12:53

+0

過去,我們使用了MD5的1/2,將其轉換爲64位整數,並將其存儲在數據庫中(在這種情況下,性能非常關鍵,具有> 100,000,000條記錄。 – gahooa 2009-08-19 23:13:38

27

字符串的內置散列函數相當無衝突,也相當短。它有2**32值,所以碰到碰撞的可能性很小(如果使用它的abs值,它將只有2**31的值)。

您一直在尋求最短的散列函數。這肯定是

def hash(s): 
    return 0 

,但我想你沒有真正的本意......

+4

我想避免碰撞:-) – u0b34a0f6ae 2009-08-19 22:58:41

+4

在http://roflcopter.pl/5257上找到:D – Frizi 2011-09-04 11:58:32

0

短哈希意味着你可以有兩個不同的文件相同的哈希。大哈希也可能發生,但其方式更爲罕見。 也許這些文件名應根據其他參考文件而有所不同,例如microtime(除非這些文件可能創建得太快)。

7

你可以通過簡單的截斷來縮短你喜歡的任何哈希。 md5始終是32位十六進制數字,但它的任意子字符串(或任何其他散列)具有散列的適當品質:相等的值產生相等的散列值,並且值散佈在一堆中。

+0

截斷得越多,相同哈希值的機率就越高兩個不同的文件。問題是「什麼賠率是可以接受的?」當你截斷時,你會遭受「誤報」:哈希匹配,但對象不同。 – 2009-08-20 10:08:51

+0

是的,確切地說。使用任何散列,您需要確定碰撞的風險是否可以接受,並評估風險。 – 2009-08-20 11:59:03

1

對於緩存對象,我們使用hashlib.sha1.hexdigest(),它會產生更長的字符串,並取得很好的成功。無論如何,沒有人真的在看緩存文件。

1

考慮到您的使用案例,如果您沒有將自己的心設置爲使用單獨的緩存文件並且您的開發路徑不太遠,則可以考慮使用shelve模塊。

這將爲您提供一個持久性字典(存儲在單個dbm文件中),用於存儲對象。酸洗/取出是透明地執行的,你不必擔心哈希,碰撞,文件I/O等。

對於擱置字典鍵,你只需使用repr(obj)並讓shelve處理爲你藏着你的物品。一個簡單的例子:

import shelve 
cache = shelve.open('cache') 
t = (1,2,3) 
i = 10 
cache[repr(t)] = t 
cache[repr(i)] = i 
print cache 
# {'(1, 2, 3)': (1, 2, 3), '10': 10} 
cache.close() 

cache = shelve.open('cache') 
print cache 
#>>> {'(1, 2, 3)': (1, 2, 3), '10': 10} 
print cache[repr(10)] 
#>>> 10