我正在开发一个使用十六进制字符(例如 64fd54ad29)将图像 URL 散列为 10 个字符串的程序。
它是用 Python 编写的,哈希计算如下:
def hash_short(self, url):
return hashlib.sha1(url).hexdigest()[:10]
我担心与如此短的哈希冲突。我预计在大约 100 万个哈希后会发生碰撞,但是当我运行蛮力时,我需要 1000 万个哈希。
计算
一个十六进制数字有 16 个可能的值,即 2^4。十个字符我有 2^40 种可能性,或 40 位熵。
要获得 1 的概率,我们需要查看 2^40 + 1 个 URL(根据鸽巢原理),但我们预计会更早发生冲突。
n 位哈希的生日攻击(即暴力破解)将在 2^(n/2) 次尝试后发现冲突。因此,我们将在大约 2^20 个 URL(即 1,048,576)后看到一次冲突。
蛮力
我编写了一个简单的 Python 脚本,它遍历一长串 URL,并将每个哈希值与我以前见过的哈希值进行比较。我花了 10,800,000 个 URL 才找到我的第一个冲突:"http://c69025.r25.cf3.rackcdn.com/_image1/_Model/34897.jpg"
并且"http://media.editd.com/assets/matrix/full/72f9a997b67c65c66f4adc769ee0a127d1db25eb.jpg"
都散列到"ba2be44bd1"
.
import hashlib
import json
def calculate_short_hash(url):
return hashlib.sha1(url).hexdigest()[:10]
def url_from_json(json_string):
return json.loads(json_string)['image_url']
if __name__ == '__main__':
short_hashes = set()
for i, line in enumerate(open('urls.all')):
short_hash = calculate_short_hash(url_from_json(line))
if short_hash in short_hashes:
print "Already seen: %s" % short_hash
break
else:
short_hashes.add(short_hash)
if i % 100000 == 0:
print "Processed %d lines" % (i,)
概括
要么我的数学不正确,要么我很不走运。它是哪一个?我到底有多倒霉?