Source: One-Time Pad
Part 1: Calculate a series of MD5 hashes (the same as Day 5). A hash is considered valid if it contains a triple (three characters in a row) and somewhere in the next 1000 hashes there is a quintuple of that same character.
What index produces the 64th key?
We’ll use the same hash function as last time, but this time, we will memoize the results with functools.lru_cache
. This means that when we generate the next 1000 hashes ahead of time looking for quintuples, we don’t have to redo the hashes again later. Hashes are pretty quick already, but this still has significant performance gains.
@functools.lru_cache(None)
def hash(n):
value = '{}{}'.format(args.salt, n)
return hashlib.md5(value.encode()).hexdigest()
After that, we’re just looking for 64 keys:
keys = set()
for i in naturals():
key = hash(i)
for triple in re.findall(r'(.)\1\1', key):
quintuple = triple * 5
for j in range(i + 1, i + 1001):
if quintuple in hash(j, args.stretch):
keys.add(key)
break
if len(keys) >= args.count:
break
print('{} keys generated after {} hashes'.format(len(keys), i))
To test the timing, we’ll remove the cache:
$ # With cache
$ time python3 bad-one-time-pads.py --salt cuanljph --count 64
64 keys generated after 23769 hashes
1.40 real 1.20 user 0.04 sys
$ # Without cache
$ time python3 bad-one-time-pads.py --salt cuanljph --count 64
64 keys generated after 23769 hashes
12.16 real 10.40 user 0.26 sys
Part 2: Implement key stretching. Repeat the MD5 hash 2017 times.
Mostly, we need to update the hash function and pass in the new parameter:
@functools.lru_cache(None)
def hash(n, repeat = 1):
value = '{}{}'.format(args.salt, n)
for i in range(repeat):
value = hashlib.md5(value.encode()).hexdigest()
return value
for i in naturals():
key = hash(i, args.stretch)
...
Nothing else changes. The caching really helps this time:
$ # With cache
$ time python3 bad-one-time-pads.py --salt cuanljph --count 64 --stretch 2017
64 keys generated after 20606 hashes
76.37 real 65.86 user 1.61 sys
$ # Without cache
$ time python3 bad-one-time-pads.py --salt cuanljph --count 64 --stretch 2017
64 keys generated after 20606 hashes
6128.07 real 6016.67 user 20.45 sys
Ouch.