web-dev-qa-db-ja.com

一意性と速度に最適なハッシュアルゴリズムはどれですか。

一意性と速度に最適なハッシュアルゴリズムはどれですか。例(良い)は、ハッシュ辞書を含みます。

SHA-256 のようなものがあることは知っていますが、これらのアルゴリズムはで設計されていますsecureこれは通常、より少ないuniqueであるアルゴリズムよりも遅いことを意味します。高速になるように設計されたハッシュアルゴリズムが必要ですが、衝突を避けるためにかなりユニークなままです。

1444
Earlz

いくつかの異なるアルゴリズムをテストし、速度と衝突の数を測定しました。

私は3つの異なるキーセットを使用しました。

コーパスごとに、衝突の数とハッシュに費やされた平均時間が記録されました。

私がテストした:

結果

各結果には、平均ハッシュ時間と衝突の数が含まれています

Hash           Lowercase      Random UUID  Numbers
=============  =============  ===========  ==============
Murmur            145 ns      259 ns          92 ns
                    6 collis    5 collis       0 collis
FNV-1a            152 ns      504 ns          86 ns
                    4 collis    4 collis       0 collis
FNV-1             184 ns      730 ns          92 ns
                    1 collis    5 collis       0 collis▪
DBJ2a             158 ns      443 ns          91 ns
                    5 collis    6 collis       0 collis▪▪▪
DJB2              156 ns      437 ns          93 ns
                    7 collis    6 collis       0 collis▪▪▪
SDBM              148 ns      484 ns          90 ns
                    4 collis    6 collis       0 collis**
SuperFastHash     164 ns      344 ns         118 ns
                   85 collis    4 collis   18742 collis
CRC32             250 ns      946 ns         130 ns
                    2 collis    0 collis       0 collis
LoseLose          338 ns        -             -
               215178 collis

メモ

衝突は実際に起こりますか?

はい。ハッシュの衝突が実際に発生するかどうかを確認するためのテストプログラムの作成を開始しました。これは単なる理論上の構成ではありません。彼らは確かに起こります:

FNV-1コリジョン

  • creamwovequistsと衝突します

FNV-1aの衝突

  • costarringliquidと衝突します
  • declinatemacallumsと衝突します
  • altaragezinkeと衝突します
  • altarageszinkesと衝突します

Murmur2の衝突

  • cataractperitiと衝突します
  • roquetteskivieと衝突します
  • shawlstormboundと衝突します
  • dowlasestramontaneと衝突します
  • cricketingstwangerと衝突します
  • longanswhigsと衝突します

DJB2衝突

  • hetairasmentionerと衝突します
  • heliotropesneurosporaと衝突します
  • depravementserafinsと衝突します
  • stylistsubgeneraと衝突します
  • joyfulsynapheaと衝突します
  • redescribeduritesと衝突します
  • dramvivencyと衝突します

DJB2aコリジョン

  • haggadotloathsomenessesと衝突します
  • adorablenessesrentabilityと衝突します
  • playwrightsnushと衝突します
  • playwrightingsnushingと衝突します
  • treponematoseswaterbedsと衝突します

CRC32コリジョン

  • coddinggnuと衝突します
  • exhibitersschlagerと衝突します

SuperFastHashコリジョン

  • dahabiahdrapabilityと衝突します
  • encharmenclaveと衝突します
  • grahamsgramaryと衝突します
  • ... 79の衝突を切り取る...
  • nightvigilと衝突します
  • nightsvigilsと衝突します
  • finksvinicと衝突します

ランダムネス化

他の主観的な尺度は、ハッシュのランダムな分布です。結果のHashTableをマッピングすると、データがどの程度均等に分散されているかがわかります。テーブルを線形にマッピングすると、すべてのハッシュ関数が適切な分布を示します。

Enter image description here

または Hilbert MapXKCDは常に関連しています )として:

Enter image description here

数字の文字列("1""2"、...、"216553")をハッシュする場合(例: 郵便番号 )を除いて、ほとんどのパターンが出現し始めますハッシュアルゴリズムの:

[〜#〜] sdbm [〜#〜]

Enter image description here

DJB2a

Enter image description here

FNV-1

Enter image description here

FNV-1aを除くすべて、それでも私にはかなりランダムに見えます:

Enter image description here

実際、Murmur2Numbersの方がFNV-1aよりもランダム性が優れているようです:

Enter image description here

FNV-1a "number"マップを見ると、think微妙な垂直パターンが見えます。 Murmurでは、パターンはまったく見えません。どう思いますか?


表の余分な*は、ランダム性がいかに悪いかを示しています。 FNV-1aが最高で、DJB2xが最低です:

      Murmur2: .
       FNV-1a: .
        FNV-1: ▪
         DJB2: ▪▪
        DJB2a: ▪▪
         SDBM: ▪▪▪
SuperFastHash: .
          CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
     Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
                                        ▪
                                 ▪▪▪▪▪▪▪▪▪▪▪▪▪
                        ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
          ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

私はもともと、このプログラムを書いて、衝突について心配することをする必要があるかどうかを判断しました。

そして、ハッシュ関数が十分にランダムであることを確認するようになりました。

FNV-1aアルゴリズム

FNV1ハッシュには、32、64、128、256、512、1024ビットハッシュを返すバリアントがあります。

FNV-1aアルゴリズム は次のとおりです。

hash = FNV_offset_basis
for each octetOfData to be hashed
    hash = hash xor octetOfData
    hash = hash * FNV_prime
return hash

定数FNV_offset_basisおよびFNV_primeは、必要な戻りハッシュサイズによって異なります。

Hash Size  
===========
32-bit
    prime: 2^24 + 2^8 + 0x93 = 16777619
    offset: 2166136261
64-bit
    prime: 2^40 + 2^8 + 0xb3 = 1099511628211
    offset: 14695981039346656037
128-bit
    prime: 2^88 + 2^8 + 0x3b = 309485009821345068724781371
    offset: 144066263297769815596495629667062367629
256-bit
    prime: 2^168 + 2^8 + 0x63 = 374144419156711147060143317175368453031918731002211
    offset: 100029257958052580907070968620625704837092796014241193945225284501741471925557
512-bit
    prime: 2^344 + 2^8 + 0x57 = 35835915874844867368919076489095108449946327955754392558399825615420669938882575126094039892345713852759
    offset: 9659303129496669498009435400716310466090418745672637896108374329434462657994582932197716438449813051892206539805784495328239340083876191928701583869517785
1024-bit
    prime: 2^680 + 2^8 + 0x8d = 5016456510113118655434598811035278955030765345404790744303017523831112055108147451509157692220295382716162651878526895249385292291816524375083746691371804094271873160484737966720260389217684476157468082573
    offset: 1419779506494762106872207064140321832088062279544193396087847491461758272325229673230371772250864096521202355549365628174669108571814760471015076148029755969804077320157692458563003215304957150157403644460363550505412711285966361610267868082893823963790439336411086884584107735010676915

詳細は FNVのメインページ を参照してください。

私の結果はすべて32ビットバリアントでのものです。

FNV-1aはFNV-1aより優れていますか?

いいえ。FNV-1aの方がずっと優れています。英語の単語コーパスを使用すると、FNV-1aとの衝突がさらに発生しました。

Hash    Word Collisions
======  ===============
FNV-1   1
FNV-1a  4

次に、小文字と大文字を比較します。

Hash    lowercase Word Collisions  UPPERCASE Word collisions
======  =========================  =========================
FNV-1   1                          9
FNV-1a  4                          11

この場合、FNV-1aはFN-1よりも "400%"悪いわけではなく、20%だけ悪くなっています。

さらに重要なことは、衝突に関しては2つのクラスのアルゴリズムがあるということです。

  • まれな衝突:FNV-1、FNV-1a、DJB2、DJB2a、SDBM
  • 衝突の一般的な:SuperFastHash、Loselose

そして、ハッシュがどのように均等に分散されるかがあります:

  • 優れた分布:Murmur2、FNV-1a、SuperFastHas
  • 優れた分布:FNV-1
  • 良い分布:SDBM、DJB2、DJB2a
  • 恐ろしい分布:Loselose

更新

つぶやく? 確かに、なぜそうではないのか


更新

@whatshisnameは、CRC32がどのように機能するか疑問に思い、表に数値を追加しました。

CRC32はかなり良いです。衝突はほとんどありませんが、速度は遅く、1kルックアップテーブルのオーバーヘッドです。

CRC配布に関するすべての誤ったものを切り取る-私の悪い


今日まで、私はFNV-1aをデファクトハッシュテーブルハッシュアルゴリズムとして使用していました。しかし、今はMurmur2に切り替えています。

  • もっと早く
  • 入力のすべてのクラスのより良いrandomnessification

そして、私は本当に、本当にSuperFastHashアルゴリズムに問題があることを望んでいます ;それは、それがそうであるように人気があるにはあまりにも悪いです。

更新:From GoogleのMurmurHash3ホームページ

(1)-SuperFastHashのコリジョンプロパティは非常に貧弱ですが、他の場所で文書化されています。

だから私だけじゃないかな。

更新:Murmurが他より速い理由に気付きました。 MurmurHash2は一度に4バイトで動作します。ほとんどのアルゴリズムはbyte by byteです:

for each octet in Key
   AddTheOctetToTheHash

これは、キーが長くなると、Murmurが光る機会を得ることを意味します。


更新

GUIDはランダムではなく一意になるように設計されています

Raymond Chenによるタイムリーな投稿は、 "random"GUIDがそのランダム性のために使用されることを意図していないという事実を繰り返し述べています。それら、またはそれらのサブセットは、ハッシュキーとして不適切です。

バージョン4 GUIDアルゴリズムは、アルゴリズムが乱数ジェネレーターの品質を指定していないため、予測不可能であることが保証されていません。 GUIDが示唆する主要な研究が含まれています ジェネレーターは暗号的に強力ではないため、乱数ジェネレーターの状態の知識に基づいて将来および以前のGUIDを予測できることを示しています。

ランダムメスは衝突回避と同じではありません。これが、「ランダムな」GUIDのサブセットを取得することによって独自の「ハッシュ」アルゴリズムを発明しようとするのが誤りであろう理由です。

int HashKeyFromGuid(Guid type4uuid)
{
   //A "4" is put somewhere in the GUID.
   //I can't remember exactly where, but it doesn't matter for
   //the illustrative purposes of this pseudocode
   int guidVersion = ((type4uuid.D3 & 0x0f00) >> 8);
   Assert(guidVersion == 4);

   return (int)GetFirstFourBytesOfGuid(type4uuid);
}

:繰り返しますが、 "random GUID"を引用符で囲みます。 GUIDの。より正確な説明はType 4 UUIDです。しかし、タイプ4、またはタイプ1、3、および5が何であるかは誰にもわかりません。したがって、それらを「ランダムな」GUIDと呼ぶ方が簡単です。

すべての英単語ミラー

2530
Ian Boyd

不変の辞書からハッシュマップを作成する場合は、完全なハッシュを検討することをお勧めします https://en.wikipedia.org/wiki/Perfect_hash_function -ハッシュ関数の構築中にハッシュテーブルを使用すると、特定のデータセットについて、衝突がないことを保証できます。

61
Damien

ここ はハッシュ関数のリストですが、短いバージョンは次のとおりです。

良いハッシュ関数を使いたいだけで、待つことができない場合は、djb2は、私が知っている最高の文字列ハッシュ関数の1つです。キーとテーブルサイズのさまざまなセットで優れた分散と速度を実現

unsigned long
hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}
34
Dean Harding

CityHash by Googleは、探しているアルゴリズムです。暗号化には適していませんが、一意のハッシュを生成するのには適しています。

詳細については ブログコードはこちらから入手可能 を参照してください。

CityHashはC++で書かれています。 プレーンCポート もあります。

2ビットのサポートについて:

すべてのCityHash関数は64ビットプロセッサ用に調整されています。つまり、32ビットコードで実行されます(SSE4.2を使用する新しいものを除く)。彼らはしかし、あまり速くはありません。 Murmurなどを32ビットコードで使用することもできます。

29
Vipin Parakkat

ファイルをハッシュするときに、さまざまなハッシュアルゴリズムの短い速度の比較をプロットしました。

すべてのファイルはtmpfsに保存されているため、個々のプロットは読み取り方法がわずかに異なるだけで、ここでは無視できます。したがって、疑問に思っているのであれば、ベンチマークはIO制約ではありませんでした。

アルゴリズムは次のとおりです:SpookyHash, CityHash, Murmur3, MD5, SHA{1,256,512}

結論:

  • Murmur3、Cityhash、およびSpookyのような非暗号化ハッシュ関数は非常に接近しています。 Cityhashは、SSE 4.2s CRC命令を使用して、CPUにはないため、CPUの方が高速である可能性があります。私の場合、SpookyHashは常にCityHashよりも少し前でした。
  • SHA256はMD5およびSHA1の 衝突の脆弱性 に対してより安全かもしれませんが、MD5は暗号化ハッシュ関数を使用するときに良いトレードオフのようです。
  • すべてのアルゴリズムの複雑さは線形です-ブロックごとに機能するため、これは実際に驚くことではありません。 (私は、読み取り方法が違いを生むかどうかを見たかったので、右端の値を比較することができます)。
  • SHA256はSHA512よりも低速でした。
  • ハッシュ関数のランダム性については調べませんでした。しかし here は、 Ian Boydsの回答 にないハッシュ関数の優れた比較です。これは、CityHashがいくつかの問題を抱えていることを指摘しています。

プロットに使用されるソース:

21
Sahib

SHAアルゴリズム(SHA-256を含む)は設計高速です。

実際、速度が問題になる場合があります。特に、パスワードから派生したトークンを格納するための一般的な手法は、標準の高速ハッシュアルゴリズムを10,000回実行することです(...のハッシュのハッシュのハッシュを保存する)。

#!/usr/bin/env Ruby
require 'securerandom'
require 'digest'
require 'benchmark'

def run_random_digest(digest, count)
  v = SecureRandom.random_bytes(digest.block_length)
  count.times { v = digest.digest(v) }
  v
end

Benchmark.bmbm do |x|
  x.report { run_random_digest(Digest::SHA256.new, 1_000_000) }
end

出力:

Rehearsal ------------------------------------
   1.480000   0.000000   1.480000 (  1.391229)
--------------------------- total: 1.480000sec

       user     system      total        real
   1.400000   0.000000   1.400000 (  1.382016)
18
yfeldblum

私はSHA-256のようなものがあることを知っていますが、これらのアルゴリズムは設計されたである安全です。これは通常、より少ないunique

暗号化ハッシュ関数がより一意であるという仮定は誤りであり、実際には、実際には逆方向であることが多いことが示されています。実は:

  1. 暗号化ハッシュ関数は、理想的にはランダムと区別できないである必要があります。
  2. ただし、暗号化されていないハッシュ関数では、可能性の高い入力と良好に相互作用するが望ましいです。

つまり、非暗号化ハッシュ関数は、「適切な」データセット、つまり設計されたデータセットの暗号化ハッシュ関数よりも衝突が少ないである可能性があります。

実際に、Ian Boydの回答のデータと少しの計算で Birthday problem というデータを使用してこれを実証できます。セットからランダムにn整数を選択した場合に予想される衝突ペア数の数式[1, d]はこれです(Wikipediaから取得):

n - d + d * ((d - 1) / d)^n

n = 216,553およびd = 2 ^ 32を接続すると、約5.5予想される衝突が得られます。 Ianのテストは主にその近傍の結果を示していますが、劇的な例外が1つあります。ほとんどの関数は、連続数テストでゼロ衝突を得ました。ランダムに216,553個の32ビット数を選択し、衝突をゼロにする確率は、約0.43%です。そして、これは1つの関数に限られます。ここでは、衝突のない5つの異なるハッシュ関数ファミリがあります。

つまり、ここで確認しているのは、Ianがテストしたハッシュがfavorablyと連続した数値のデータセットと相互作用しているということです。つまり、最小の異なる入力を分散しています。理想的な暗号ハッシュ関数よりも広く。 (側注:これは、数値データセットでFNV-1aとMurmurHash2が彼に「ランダムに見える」というIanのグラフィカル評価は、彼自身のデータから反論できることを意味します。両方ハッシュ関数、驚くほどランダムではありません!)

これはハッシュ関数の多くの用途にとって望ましい動作であるため、これは驚きではありません。たとえば、ハッシュテーブルのキーはよく似ています。 Ianの回答には次のような記述があります MSNがかつて郵便番号のハッシュテーブルで抱えていた問題 。これは、likely入力の衝突回避がランダムのような動作よりも優れている用途です。

ここでの有益なもう1つの比較は、CRCと暗号化ハッシュ関数の設計目標の対比です。

  • CRCはノイズの多い通信チャネルに起因するエラーをキャッチするように設計されています。
  • 暗号ハッシュは、限られた計算リソースが割り当てられているが、任意に非常に賢い悪意のある攻撃者による変更をキャッチするように設計されています。

したがって、CRCの場合も、最小限の異なる入力でランダムよりも衝突が少ないのはgoodです。暗号ハッシュでは、これはノーノーです!

15
sacundim

SipHash を使用します。 多くの望ましい特性があります:

  • Fast。最適化された実装は、バイトあたり約1サイクルかかります。

  • 安全です。SipHashは強力なPRF(疑似ランダム関数)です。これは、ランダムな関数と区別がつかないことを意味します(128ビットの秘密鍵を知らない限り)。したがって:

    • 衝突によりハッシュテーブルプローブが線形時間になることを心配する必要はありません。 SipHashを使用すると、入力に関係なく、平均で平均ケースのパフォーマンスが得られることを知っています

    • ハッシュベースのサービス拒否攻撃に対する耐性。

    • SipHash(特に128ビット出力のバージョン)をMAC(メッセージ認証コード)として使用できます。メッセージとSipHashタグを受け取り、そのタグが秘密鍵を使用してSipHashを実行した場合と同じである場合、ハッシュを作成した人が秘密鍵も所有しており、メッセージもハッシュはそれ以来変更されています。

10
Demi

ハッシュするデータによって異なります。一部のハッシュは、テキストなどの特定のデータでより適切に機能します。一部のハッシュアルゴリズムは、特定のデータに適するように特別に設計されています。

Paul Hsiehがかつて作った 高速ハッシュ 。彼はソースコードと説明をリストしています。しかし、それはすでに打ち負かされていました。 :)

9
user712092

Javaは this 単純な乗加算アルゴリズムを使用します。

Stringオブジェクトのハッシュコードは次のように計算されます

 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

int演算を使用して、ここでs[i]は文字列のi番目の文字、nは文字列の長さ、^は、べき乗を示します。 (空の文字列のハッシュ値はゼロです。)

おそらくはるかに優れたものが存在しますが、これはかなり広く普及しており、速度と一意性の間の適切なトレードオフのようです。

6
biziclop

まず、なぜ独自のハッシュを実装する必要があるのですか?ほとんどのタスクでは、利用可能な実装があることを前提として、標準ライブラリのデータ構造で良い結果が得られるはずです(自分の教育のためにこれを行っているだけの場合を除く)。

実際のハッシュアルゴリズムに関する限り、私の個人的なお気に入りはFNVです。 1

以下は、Cでの32ビットバージョンの実装例です。

unsigned long int FNV_hash(void* dataToHash, unsigned long int length)
{
  unsigned char* p = (unsigned char *) dataToHash;
  unsigned long int h = 2166136261UL;
  unsigned long int i;

  for(i = 0; i < length; i++)
    h = (h * 16777619) ^ p[i] ;

  return h;
}
4
user17754