一意性と速度に最適なハッシュアルゴリズムはどれですか?例(良い)にはハッシュ辞書が含まれます。
私はSHA-256などのようなものがあることを知っていますが、これらのアルゴリズムは安全であるように設計されています。これは通常、一意性の低いアルゴリズムよりも遅いことを意味します。衝突を避けるために、高速でありながらかなりユニークなハッシュアルゴリズムを設計したいと考えています。
一意性と速度に最適なハッシュアルゴリズムはどれですか?例(良い)にはハッシュ辞書が含まれます。
私はSHA-256などのようなものがあることを知っていますが、これらのアルゴリズムは安全であるように設計されています。これは通常、一意性の低いアルゴリズムよりも遅いことを意味します。衝突を避けるために、高速でありながらかなりユニークなハッシュアルゴリズムを設計したいと考えています。
回答:
速度と衝突回数を測定して、いくつかの異なるアルゴリズムをテストしました。
3つの異なるキーセットを使用しました。
"1"
に"216553"
(ZIPコードを考え、どのように貧しいハッシュがmsn.comを降ろしました)各コーパスについて、衝突の数とハッシュに費やされた平均時間を記録しました。
私はテストしました:
各結果には、平均ハッシュ時間と衝突数が含まれます
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の衝突
creamwove
衝突する quists
FNV-1aの衝突
costarring
衝突する liquid
declinate
衝突する macallums
altarage
衝突する zinke
altarages
衝突する zinkes
Murmur2の衝突
cataract
衝突する periti
roquette
衝突する skivie
shawl
衝突する stormbound
dowlases
衝突する tramontane
cricketings
衝突する twanger
longans
衝突する whigs
DJB2コリジョン
hetairas
衝突する mentioner
heliotropes
衝突する neurospora
depravement
衝突する serafins
stylist
衝突する subgenera
joyful
衝突する synaphea
redescribed
衝突する urites
dram
衝突する vivency
DJB2aの衝突
haggadot
衝突する loathsomenesses
adorablenesses
衝突する rentability
playwright
衝突する snush
playwrighting
衝突する snushing
treponematoses
衝突する waterbeds
CRC32コリジョン
codding
衝突する gnu
exhibiters
衝突する schlager
SuperFastHashの衝突
dahabiah
衝突する drapability
encharm
衝突する enclave
grahams
衝突する gramary
night
衝突する vigil
nights
衝突する vigils
finks
衝突する vinic
もう1つの主観的な尺度は、ハッシュがどの程度ランダムに分散されているかです。結果のHashTablesをマッピングすると、データが均等に分散されることがわかります。テーブルを線形にマッピングすると、すべてのハッシュ関数が良好な分布を示します。
または、ヒルベルトマップとして(XKCDは常に関連します):
番号文字列(ハッシュする場合を除き"1"
、"2"
、...、 "216553"
)(例えば、郵便番号パターンは、ハッシュアルゴリズムのほとんどに出現し始めます)、:
SDBM:
DJB2a:
FNV-1:
FNV-1aを除くすべては、まだ私にはかなりランダムに見えます:
実際、Murmur2は次のものよりもランダム性がさらに優れているようNumbers
ですFNV-1a
。
私が見たとき
FNV-1a
「番号」マップ、私は考えて、私は微妙な縦パターンを参照してください。つぶやきでは、パターンはまったく見られません。どう思いますか?
*
表の余分は、ランダム性がどれほど悪いかを示しています。FNV-1a
最高であること、そしてDJB2x
最悪のこと:
Murmur2: .
FNV-1a: .
FNV-1: ▪
DJB2: ▪▪
DJB2a: ▪▪
SDBM: ▪▪▪
SuperFastHash: .
CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪
▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
私はもともとこのプログラムを書いて、衝突について心配する必要があるかどうかを判断しました。
そして、ハッシュ関数が十分にランダムであることを確認することになりました。
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との衝突がさらに多くなりました。
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つのクラスのアルゴリズムがあることだと思います。
そして、ハッシュがどの程度均等に分散されているかがあります。
更新
つぶやき?もちろん
更新
@whatshisnameは、CRC32がどのように機能するか疑問に思い、表に数字を追加しました。
CRC32はかなり良いです。衝突はほとんどありませんが、速度が遅く、1kルックアップテーブルのオーバーヘッドです。
CRC分布に関するすべての誤った情報を切り取る-悪い
今日まで、事実上のハッシュテーブルハッシュアルゴリズムとしてFNV-1aを使用していました。しかし、今私はMurmur2に切り替えています:
そして、本当にSuperFastHash
見つけたアルゴリズムに何か問題があることを本当に願っています。そんなに人気があるのは残念だ。
更新:GoogleのMurmurHash3ホームページから:
(1)-SuperFastHashの衝突特性は非常に低く、他の場所で文書化されています。
だから、私だけではないようです。
更新:なぜMurmur
他のものよりも高速であることに気付きました。MurmurHash2は、一度に4バイトで動作します。ほとんどのアルゴリズムはバイトごとです:
for each octet in Key
AddTheOctetToTheHash
これは、キーが長くなるとMurmurが光るチャンスを得ることを意味します。
更新
Raymond Chenによるタイムリーな投稿では、「ランダムな」 GUIDがランダム性のために使用されることを意図していないという事実を繰り返し述べています。それら、またはそれらのサブセットは、ハッシュキーとして不適切です。
バージョン4のGUIDアルゴリズムであっても、アルゴリズムは乱数ジェネレーターの品質を指定しないため、予測不能であることは保証されていません。GUIDのWikipediaの記事には、乱数ジェネレーターの状態に関する知識に基づいて、ジェネレーターが暗号的に強力ではないため、将来および以前のGUIDを予測できることを示唆する主要な研究が含まれています。
Randomessは衝突回避と同じではありません。これが、「ランダム」な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);
}
注:繰り返しますが、「ランダムなGUID」を引用符で囲みます。これは、GUIDの「ランダムな」バリアントであるためです。より正確な説明は次のようになりますType 4 UUID
。しかし、タイプ4またはタイプ1、3、5が何であるかは誰にもわかりません。したがって、それらを「ランダムな」GUIDと呼ぶ方が簡単です。
変わらない辞書からハッシュマップを作成する場合は、完全なハッシュhttps://en.wikipedia.org/wiki/Perfect_hash_functionを検討することをお勧めします -ハッシュ関数とハッシュテーブルの構築中に、特定のデータセットについては、衝突はありません。
ハッシュ関数のリストを次に示しますが、短いバージョンは次のとおりです。
あなたが良いハッシュ関数を持ちたいだけで、待つことができない場合、
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;
}
GoogleのCityHashは、探しているアルゴリズムです。暗号化には適していませんが、一意のハッシュの生成には適しています。
詳細についてはブログをご覧ください。コードはこちらから入手できます。
CityHashはC ++で書かれています。また、プレーンなCポートもあります。
すべてのCityHash関数は、64ビットプロセッサ用に調整されています。ただし、これらは32ビットコードで実行されます(SSE4.2を使用する新しいものを除く)。しかし、それらはあまり速くありません。32ビットコードでMurmurなどを使用することもできます。
plain C port
リンクが壊れています
ファイルをハッシュするときのさまざまなハッシュアルゴリズムの短い速度の比較をプロットしました。
すべてのファイルはtmpfsに保存されているため、個々のプロットは読み取り方法がわずかに異なるだけで、ここでは無視できます。したがって、疑問がある場合は、ベンチマークはIOにバインドされていません。
アルゴリズムは次のとおりSpookyHash, CityHash, Murmur3, MD5, SHA{1,256,512}
です。
結論:
CRC
私のCPUにはないSSE 4.2s 命令を使用したCPUでは、Cityhashの方が高速になる可能性があることに注意してください。SpookyHashは私の場合、CityHashの前は常にほんの少しでした。プロットに使用されるソース:
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)
bcrypt
。適切なツールを使用してください。
.rodata
および/または状態のコストが高いことを意味します。ハッシュテーブル用のアルゴリズムが必要な場合、通常は非常に短いキーと多くのキーがありますが、暗号化の追加の保証は必要ありません。調整済みのJenkinsを1つずつ使用します。
私はSHA-256などのようなものがあることを知っていますが、これらのアルゴリズムは安全になるように設計されています。これは通常、一意性の低いアルゴリズムよりも遅いことを意味します。
暗号化ハッシュ関数がより一意であるという仮定は間違っており、実際には実際にはしばしば後方にあることが示されます。実際には:
つまり、非暗号化ハッシュ関数は、「適切な」データセット(設計されたデータセット)の暗号化ハッシュ関数よりも衝突が少ない可能性があります。
Ian Boydの回答のデータと少しの数学:誕生日問題で実際にこれを実証できます。n
セットから整数をランダムに選択した場合の衝突ペアの予想数の式は次のとおりです[1, d]
(ウィキペディアから取得)。
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がテストしたハッシュが連続番号データセットと良好に相互作用していることです。つまり、理想的な暗号化ハッシュ関数よりも広く、最小限の異なる入力を分散しています。(サイドノート:これは数字のデータセットで彼に「ランダムに見える」イアンのグラフィカルな評価というFNV-1aおよびMurmurHash2という意味では、彼自身のデータから反論することができ、そのサイズのデータセット上のゼロの衝突のために。両方のハッシュ関数、驚くほど非ランダムです!)
これは、ハッシュ関数の多くの使用にとって望ましい動作であるため、驚くことではありません。たとえば、ハッシュテーブルキーはよく似ています。Ianの回答は、MSNがかつて郵便番号ハッシュテーブルで抱えていた問題に言及しています。これは、可能性のある入力での衝突回避がランダムのような動作に勝る用途です。
ここでのもう1つの有益な比較は、CRCと暗号化ハッシュ関数の設計目標の対照です。
したがって、CRCの場合、最小限の異なる入力で、ランダムよりもコリジョンが少ない方が良いです。暗号ハッシュでは、これは不可です!
SipHashを使用します。それは持っている多くの望ましい特性を:
速い。 最適化された実装には、1バイトあたり約1サイクルかかります。
安全。 SipHashは強力なPRF(疑似ランダム関数)です。これは、ランダム関数と区別できないことを意味します(128ビットの秘密鍵を知らない限り)。したがって:
衝突によりハッシュテーブルプローブが線形時間になることを心配する必要はありません。SipHash を使用すると、入力に関係なく、平均的なケースのパフォーマンスが得られることがわかります。
ハッシュベースのサービス拒否攻撃に対する耐性。
SipHash(特に128ビット出力のバージョン)をMAC(メッセージ認証コード)として使用できます。メッセージとSipHashタグを受信し、タグが秘密キーを使用してSipHashを実行した場合と同じ場合、ハッシュを作成した人も秘密キーを所有しており、メッセージも以降、ハッシュが変更されました。
ハッシュするデータに依存します。一部のハッシュは、テキストなどの特定のデータでより適切に機能します。一部のハッシュアルゴリズムは、特定のデータに適した設計になっています。
Paul Hsiehはかつて高速ハッシュを作成しました。彼はソースコードと説明をリストしています。しかし、すでにbeatられていました。:)
Javaは、この単純な乗加算アルゴリズムを使用します。
Stringオブジェクトのハッシュコードは次のように計算されます
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
int算術を使用します。ここ
s[i]
で、は文字列のi番目の文字であり、文字列n
の長さであり、^
べき乗を示します。(空の文字列のハッシュ値はゼロです。)
おそらくもっと良いものがそこにありますが、これはかなり広範であり、速度と一意性の間の良いトレードオフのようです。
まず、なぜ独自のハッシュを実装する必要があるのですか?ほとんどのタスクでは、利用可能な実装があると仮定して、標準ライブラリのデータ構造を使用して良好な結果を得る必要があります(独自の教育のためにこれを行っている場合を除きます)。
実際のハッシュアルゴリズムに関する限り、私の個人的なお気に入りは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;
}
*
and の順序を交換し^
ます。h = (h * 16777619) ^ p[i]
==>h = (h ^ p[i]) * 16777619