漫談非加密哈希算法

哈希算法是一個大雜燴,除了 MD五、SHA1 這一類加密哈希算法(cryptographic hash),還有不少或家喻戶曉或寂寂無聞的算法。
哈希算法只需知足把一個元素映射到另外一個區間的要求。鑑於該要求是如此之低,像 Java 的 hashCode 其實也算一種哈希算法。
不過今天固然不會講 Java 的 hashCode,咱們來聊聊一些更具實用性的算法,某些場景下它們不只能夠替代加密哈希算法,甚至還有額外的優點。git

評價一個哈希算法的好壞,人們一般會引用 SMHasher 測試集的運行結果。從這裏能夠看到關於它的介紹:SMHasher wiki。此外還有人在 SMHasher 的基礎上,添加更多的測試和哈希算法:demerphq/smhasher。demerphq/smhasher 這個項目有個好處,做者把 SMhasher 運行結果放到 doc 目錄下,爲本文的分析提供了豐富的數據。github

在須要進行比較的時候,我會選擇 MD5 做爲加密哈希算法陣營的表明。緣由有三:算法

  1. MD5 一般用在不須要加密的哈希計算中,以至於有些場合下「哈希」就意味着計算 MD5。
  2. MD5 計算速度跟其餘加密哈希算法差很少。
  3. MD5 生成的哈希值是 128 比特的。這裏的哈希值指的是二進制的值,而不是 HEX 或 base64 格式化後的人類可讀的值。一般咱們提到的 32 位 MD5 是指由 32 個字符組成的,HEX 格式的 MD5。下面提到的 32 位、128 位,若是沒有特殊說明,都是指比特數。

MurMurHash

設想這樣的場景:當數據中有幾個字段相同,就能夠把它看成同類型數據。對於同類型的數據,咱們須要作去重。
一個一般的解決辦法是,使用 MD5 計算這幾個字段的指紋,而後把指紋存起來。若是當前指紋存在,則表示有同類型的數據。
這種狀況下,因爲不涉及故意的哈希碰撞,並不必定要採用加密哈希算法。(加密哈希算法的一個特色是,即便你知道哈希值,也很難僞造有一樣哈希值的文本)
而非加密哈希算法一般要比加密哈希算法快得多。若是數據量小,或者不太在乎哈希碰撞的頻率,甚至能夠選擇生成哈希值小的哈希算法,佔用更小的空間。安全

就我的而言,我偏好在這種場景裏採用 MurMurHash3,128 位的版本。理由有三:app

  1. MurMurHash3 久經沙場,主流語言裏面都能找到關於它的庫。
  2. MurMurHash3,128 位版本的哈希值是 128 位的,跟 MD5 同樣。128 位的哈希值,在數據量只有千萬級別的狀況下,基本不用擔憂碰撞。
  3. MurMurHash3 的計算速度很是快。

MurMurHash3 哈希算法是 MurMurHash 算法家族的最新一員。雖然說是「最新一員」,但距今也有五年的歷史了。不管從運算速度、結果碰撞率,仍是結果的分佈均衡程度進行評估,MurMurHash3 都算得上一個優秀的哈希算法。ide

除了 128 位版本之外,它還有生成 32 位哈希值的版本。在某些場景下,好比哈希的對象長度小於 128 位,或者存儲空間要求佔用小,或者須要把字符串轉換成一個整數,這一特性就能幫上忙。固然,32 位哈希值發生碰撞的可能性就比 128 位的要高得多。當數據量達到十萬時,就頗有可能發生碰撞。函數

貼一個簡略的 MurMurHash3 和 MD五、MurMurHash2 的 benchmark:
lua-resty-murmurhash3#benchmark性能

能夠看到,MurMurHash3 128 位版本的速度是 MD5 的十倍。有趣的是,MurMurHash3 生成 32 位哈希的用時比生成 128 位哈希的用時要長。緣由在於生成 128 位哈希的實現受益於現代處理器的特性。測試

CRC32

MurMurHash3 是個人心頭好,另外一個值得關注的是 CRC 系列哈希算法。諸位都知道,CRC 能夠用來算校驗和。除此之外,CRC 還能夠看成一個哈希函數使用。優化

目前經常使用的 CRC 算法是 CRC32,它能夠把一個字符串哈希成 32 位的值。CRC32 的碰撞率要比 MurMurHash3(32位)低,惋惜它的運算速度跟 MD5 差很少。一個 32 位的哈希值,再怎麼樣,碰撞的機率仍是比 128 位的多得多。更況且選用非加密哈希算法,運算速度每每是首先考慮的。看樣子 CRC32 要出局了。

好在 CRC 系列一貫有硬件加持。只要 CPU 支持 sse4.2 特性,就能使用 _mm_crc32_* 這一類硬件原語。(參見 Intel 的 文檔,更詳細的用法能夠在網上搜到)硬件加持以後,CRC32 的計算速度能夠達到十數倍以上的提高。換個說法,就是有硬件加持的 CRC32,比 MurMurHash3 要快。

不過要注意的是,有 sse4.2 加持的是 CRC32c,它是 CRC32 的一個變種。CRC32c 跟咱們一般用的 CRC32 並不兼容。因此若是你要編寫的程序會持久化 CRC32 哈希值,在使用硬件加速以前先關注這一點。

FNV

跟 FNV 的初次邂逅,是在 Go 的標準庫文檔裏。我很驚訝,一門主流語言的標準庫裏,竟然會有不太知名的哈希算法。從另外一個角度看,非加密哈希算法仍是有其必要性的,否則 Go 這門以實用著稱的語言也不會內置 FNV 算法了。

惋惜跟其餘哈希算法相比,FNV 並沒有出彩之處(參考 SMHasher 測試結果)。FNV 家族較新的 FNV-1a 版本,對比於 FNV-1 作了一些改善。儘管如此,除非寫的是 Go 程序,我一般都不會考慮使用它。

SipHash

大部分非加密哈希算法的改良,都集中在讓哈希速度更快更好上。SipHash 則是個異類,它的提出是爲了解決一類安全問題:hash flooding。經過讓輸出隨機化,SipHash 可以有效減緩 hash flooding 攻擊。憑藉這一點,它逐漸成爲 Ruby、Python、Rust 等語言默認的 hash 表實現的一部分。

若是你願意嘗試下新技術,能夠看看 2016 新出的 HighwayHash。它宣稱能夠達到 SipHash 同樣的效果,而且憑藉 SIMD 的加持,在運算速度上它是 SipHash 的 5.2 倍(參考來源:https://arxiv.org/abs/1612.06257)。

xxHash

爲何要用一個不知名的哈希函數?最首要的理由就是性能。對性能的追求是無止境的。若是不考慮硬件加持的 CRC32,xxHash能夠說是哈希函數性能競賽的最新一輪優勝者。

xxHash 支持生成 32 位和 64 位哈希值,多個 benchmark 顯示,其性能比 MurMurHash 的 32 位版本快接近一倍。若是程序的熱點在於哈希操做,做爲一種優化手段,xxHash 值得一試。

順便一提,MetroHash 聲稱其速度在 xxHash 之上。不過前者用的人很少,若是想嚐鮮,能夠關注下。

相關文章
相關標籤/搜索