While working on a larger project there was a need to detect some
changes happened in given data structures. Usually, you immediately
start over using the default hashing algorithm md5:
Python 3.4.5 (default, Jan 14 2017, 22:06:30)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
Let’s compare 2 strings:
>>> aa = "Hello World"
>>> bb = "Hello World"
>>> aa == bb
True
So obviouly, these strings are equal. So also hashlib should confirm this:
>>> import hashlib
>>> hashlib.md5(aa.encode()).hexdigest()
'b10a8db164e0754105b7a99be72e3fe5'
>>> hashlib.md5(bb.encode()).hexdigest()
'b10a8db164e0754105b7a99be72e3fe5'
>>> hashlib.md5(aa.encode()).digest() == hashlib.md5(bb.encode()).digest()
True
I was wondering which hashing algorithm is the fastest while still be collision safest and could checksums be a better approach?
hashing vs. checksum
hashing
Why do i need a hashing algorithm. Why not just using built in hash()
method?
I failed because everytime i restarted my Python process I received a
different hash from hash method, so this has somehow be randomized on
process startup, so i asked Google and i found this excellent post on
the python mailing
list:
... The security issue exploits Python’s dict and set implementations. Carefully crafted input can lead to extremely long computation times and denials of service. [1] Python dict and set types use hash tables to provide amortized constant time operations. Hash tables require a well-distributed hash function to spread data evenly across the hash table. The security issue is that an attacker could compute thousands of keys with colliding hashes; this causes quadratic algorithmic complexity when the hash table is constructed. To alleviate the problem, the new releases add randomization to the hashing of Python’s string types (bytes/str in Python 3 and str/unicode in Python 2), datetime.date, and datetime.datetime. This prevents an attacker from computing colliding keys of these types without access to the Python process.
Hash randomization causes the iteration order of dicts and sets to be unpredictable and differ across Python runs. Python has never guaranteed iteration order of keys in a dict or set, and applications are advised to never rely on it. ...
[1] http://www.ocert.org/advisories/ocert-2011-003.html
-- Benjamin Peterson / Python Mailing List
No good news and a anyway a bad idea to rely it.
checksum
Next try was md5. But how fast is md5 and why not using just a
cyclic redundancy check for this. So i wrote a small testscript and
included crc and adler as well just to get a feeling about the speed and
efficiency of those algorithms as well
#!/usr/bin/env python
import hashlib
import sys
import time
import zlib
algorithms = [getattr(hashlib, algo) for algo in hashlib.algorithms_guaranteed]
algorithms.append(zlib.crc32)
algorithms.append(zlib.adler32)
def run(bytes_data, algorithm, iterations=1000):
start = time.time()
for idx in range(0, iterations):
algorithm(bytes_data)
return time.time() - start
def iteration(title, sample):
print(title + ' (%d chars):' % len(sample))
values = []
for algorithm in algorithms:
duration = run(sample, algorithm)
values.append((duration, algorithm))
print('%.06fs using %s' % (duration, algorithm.__name__))
print()
return values
if __name__ == '__main__':
sample = ''.join([chr(i) for i in range(32, 123)]).encode()
print('Python: %s' % sys.version)
print('Sample: %s' % sample)
print()
iteration('Short', sample)
iteration('Long', sample * 10000)
Result
I was running these tests on an 3,4 GHz Intel Core i7 on macOS 10.12.5:
Python: 3.4.5 (default, Jan 14 2017, 22:06:30)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]
Sample string used:
b' !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz'
For short sample string with (91 chars):
0.000331s using openssl_sha512
0.000410s using openssl_md5
0.000531s using openssl_sha224
0.000524s using openssl_sha256
0.000321s using openssl_sha384
0.000418s using openssl_sha1
0.000189s using crc32
0.000153s using adler32
For long sample string above with (91000 chars):
2.102090s using openssl_sha512
1.395054s using openssl_md5
3.119225s using openssl_sha224
3.385265s using openssl_sha256
2.185888s using openssl_sha384
1.321250s using openssl_sha1
0.295018s using crc32
0.071074s using adler32
Conclusion
- Use
adler32if you have large data and speed matters. - Use
md5if you need more security and still be quite fast. - Never use
crc32,adler32ormd5if security matters.
Update
While updating my blog I ran across this again and ran the same script on my
2022 Macbook Air. What surprised me most, zlib.crc32 seems to be a hole lot
quicker on Apple Silicon than it has been on Intel. See the numbers below:
Short (91 chars):
0.000430s using openssl_sha256
0.000385s using openssl_shake_128
0.000263s using blake2b
0.000287s using openssl_sha3_256
0.000362s using openssl_sha3_224
0.000376s using openssl_sha3_384
0.000494s using openssl_sha224
0.000402s using openssl_sha1
0.000821s using openssl_sha3_512
0.000370s using openssl_shake_256
0.000381s using openssl_md5
0.000261s using openssl_sha512
0.000176s using blake2s
0.000261s using openssl_sha384
0.000120s using crc32
0.000092s using adler32
Long (910000 chars):
2.245597s using openssl_sha256
1.635454s using openssl_shake_128
2.352355s using blake2b
2.005842s using openssl_sha3_256
1.900329s using openssl_sha3_224
2.613610s using openssl_sha3_384
2.237704s using openssl_sha224
1.202555s using openssl_sha1
3.766938s using openssl_sha3_512
2.006927s using openssl_shake_256
1.527310s using openssl_md5
1.331058s using openssl_sha512
3.949191s using blake2s
1.301599s using openssl_sha384
0.030480s using crc32 # <-- look how fast this compared to above
0.100337s using adler32