GxHash is a fast and robust non-cryptographic hashing algorithm
github.com
github.com
gxhash
| 4 > 8300.74
| 8 > 16684.68
| 16 > 33121.69
| 32 > 38146.97
| 64 > 57805.72
| 128 > 49712.76
| 256 > 69277.29
| 512 > 88315.11
| 1024 > 97135.29
| 2048 > 98200.60
| 4096 > 104709.01
| 8192 > 108988.86
| 16384 > 110630.70
| 32768 > 111406.96
xxhash (twox_hash)
| 4 > 1291.20
| 8 > 2579.19
| 16 > 6029.70
| 32 > 10281.18
| 64 > 15950.47
| 128 > 22031.84
| 256 > 17929.12
| 512 > 20728.87
| 1024 > 21683.24
| 2048 > 21679.94
| 4096 > 21732.89
| 8192 > 21748.57
| 16384 > 21534.84
| 32768 > 21703.27
xxhash (xxhash-rust)
| 4 > 1968.26
| 8 > 3946.71
| 16 > 8030.94
| 32 > 13099.80
| 64 > 19073.49
| 128 > 23469.46
| 256 > 22909.73
| 512 > 33117.15
| 1024 > 42467.53
| 2048 > 54638.44
| 4096 > 63193.78
| 8192 > 68677.38
| 16384 > 71658.92
| 32768 > 71421.18 > ./target/debug/gxwords | cut -d' ' -f1 | sort | uniq -c | awk '$1>1'
2 28273056
2 8e264f8
2 f7bce777
Which then gives us > ./target/debug/gxwords | egrep '28273056|8e264f8|f7bce777' | sort
28273056 counterclaiming
28273056 oncogene's
8e264f8 ingrates
8e264f8 toadstool's
f7bce777 Carib's
f7bce777 stemming $ cat /usr/share/dict/words | wc -l
235976
$ cat /usr/share/dict/words | grep s | wc -l
101882The /usr/share/dict/words on my macOS machine doesn't seem to have any 's words in it.
oncogene's <> counterclaiming
ingrates <> toadstool's
Carib's <> stemming
all 3 pairs have one word ending in "'s", which I think is what the GGGP (?) was pointing outIn that case, the probability that, for 3 collisions, there is at least one element in every collision contains "'s" is 31%. That seems reasonable to me.
But "oncogene's" (collides with counterclaiming) and "Carib's" (collides with stemming) do.
Also I did point out that the 64 and 128 bit variants have no collisions.
On its own, yes, fair, but it was combined with the 64 and 128 bit variants which aren't uninteresting.
> As others point out, 32-bit XXH3 has the same issue.
Yes, me.
> It has nothing to do with the quality of the hash algorithm
The original post asked "I would like to see the collision distribution on /usr/share/dict/words." and that is what they got a demonstration of - some collisions on 32 bit, none on 64 or 128 bit. What else could be done to satisfy their original request in a way that would be interesting to you?
734c8981 abloom
734c8981 sating
ef336a7d contravene
ef336a7d seducersAlso the core primitive is AES which should calm most such worries.
"GxHash is a fast and robust non-cryptographic hashing algorithm"
Exactly that I should use it when I want a fast hash, but not when I want a robust cryptographic hash? In which case I can ignore your attack? (Not to discount your work, I'm just trying to understand the scope here)
So I can use it in my hash-map, or whatever O(1) lookup, but I shouldn't use it in my rewrite of SSL?
BTW in the past (20 years ago?) there have been attacks on non-cryptographic hashes used for hash maps: denial of service by creating crafted requests to HTTP servers... The parameteres would be picked maliciously and would be all ending in the same "buckets". That attack worked on both Java and PHP servers. IIRC it's been solved by adding random seeding (and I noticed that GxHash mentions it's seedable: not saying it'd counter every attack but it's already something).
There have been places where a dev chose to use a non-cryptographic hash when they should not have, because they hadn't thought of the threat model (out of ignorance, or just didnt occur to them).
But in most cases, nothing happens. They gained performance, or ease of development, etc. So it's a worthy trade off most of the time (whether they did it knowingly or not).
If a small change to the seed mixing (literally moving a line from the end to the beginning of the function) increases the security with no penalty to performance, then we might as well make the change.
> All generated hashes for a given version of GxHash are stable, meaning that for a given input the output hash will be the same across all supported platforms.
If you look at the source code, it's using it in both parts too. Interestingly, there's a discrepancy between the paper and the code. The paper says the seed is passed to the first round of the 3-round finalizer/mixer, but the code has a separate 0th round that gets the seed instead.
edit: I did the work myself as I should have in the first place, although I'm tired.
% cargo bench --bench throughput
Compiling gxhash v3.1.1 (/Users/daniel/Developer/gxhash)
Compiling fastmurmur3 v0.2.0
Finished `bench` profile [optimized] target(s) in 1.03s
Running benches/throughput/main.rs (target/release/ deps/throughput-c9580307c88ca541)
gxhash
| 4 > 3980.55
| 8 > 7961.11
| 16 > 15922.22
| 32 > 17986.93
| 64 > 25083.88
| 128 > 24548.44
| 256 > 32485.82
| 512 > 40386.43
| 1024 > 45980.96
| 2048 > 49396.93
| 4096 > 51314.47
| 8192 > 52324.80
| 16384 > 51536.52
| 32768 > 52342.91
murmur3
| 4 > 547.33
| 8 > 910.5 1
| 16 > 1752.21
| 32 > 2676.02
| 64 > 3552.02
| 128 > 4045.35
| 256 > 3975.75
| 512 > 3748.31
| 1024 > 3560.22
| 2048 > 3439.41
| 4096 > 3411.45
| 8192 > 3389.27
| 16384 > 3376.18
| 32768 > 3379.46Not sure how it compares.
In terms of speed, XXH3 is roughly the speed of CRC32 but can output 64 or 128-bit hashes (CRC64 by comparison ~3x slower). This algorithm is ~10x faster than XXH3 for small data sizes and ~2x faster for larger ones so it would be 2x faster than CRC32 for a much higher quality hash and ~6x faster than CRC64 for a much higher quality hash.
TLDR: Don’t use CRC, use at least 64-bit or 128-bit checksum, and use a good modern hash algorithm for checksumming like XXH3. I know XXH3 very specifically targets the checksum use-case. I don’t know about gxhash’s suitability for that, but I suspect that it’s equally as fine - it’s really hard to measure the quality of a 64-bit and 128-bit hash unless they’re so bad they fail obvious tests.
The note in there that the output of the algorithm is different for different SIMD widths seems to indicate that this isn't so much an algorithm as a family of related algorithms
> All generated hashes for a given version of GxHash are stable, meaning that for a given input the output hash will be the same across all supported platforms.
The github repo is more recent so maybe it wasn't stable at the time they tried to get it into smhasher from last year?
It passes.
The paper also doesn't exhaustively verify numbers when domain=codomain, which is a super common scenario and a weird omission. 32 bits is small enough to check everything.
Still an interesting construction I'll play around with some more when I have time.
Let me know if I'm wrong though, strictly an amateur here.
pub(crate) unsafe fn gxhash(input: &[u8], seed: State) -> State {
finalize(aes_encrypt(compress_all(input), seed))
}
For a hash table, you would normally use just the low bits to select a bucket, so this still protects you from collisions on just a subset of the bits, but if you can produce a large number of collisions on the exact 128-bit output, then it's not DOS-resistant.Still, I tried my best getting into the details, but I learned a lot while developing this hash function, and I still have plenty to learn especially regarding security. That is one of the reasons I made it open source: get feedback from more seasoned peers and improve this :).