···11+import sys
22+import fileinput
33+from hashlib import md5
44+from collections import defaultdict
55+66+from utils import memoize
77+88+99+@memoize
1010+def salty_md5(salt, i):
1111+ return md5(salt + str(i)).hexdigest()
1212+1313+1414+@memoize
1515+def stretched_md5(salt, i):
1616+ h = salty_md5(salt, i)
1717+ for _ in range(2016):
1818+ h = md5(h).hexdigest()
1919+2020+ return h
2121+2222+2323+def first_triple(s):
2424+ for i in range(len(s) - 2):
2525+ a, b, c = s[i:i+3]
2626+ if a == b == c:
2727+ return a
2828+2929+3030+def all_quintuplets(s):
3131+ for i in range(len(s) - 4):
3232+ a, b, c, d, e = s[i:i+5]
3333+ if a == b == c == d == e:
3434+ yield a
3535+3636+3737+def find_pad_key_64(salt, hash_fn):
3838+ valid_keys = 0
3939+ quintuplets = defaultdict(set)
4040+ i = 0
4141+4242+ while True:
4343+ digest = hash_fn(salt, i)
4444+4545+ for quint in all_quintuplets(digest):
4646+ quintuplets[i].add(quint)
4747+4848+ if i >= 1000:
4949+ n = i - 1000
5050+ triple = first_triple(hash_fn(salt, n))
5151+ for j in range(n + 1, n + 1001):
5252+ if triple in quintuplets[j]:
5353+ valid_keys += 1
5454+ sys.stdout.write('.')
5555+ sys.stdout.flush()
5656+5757+ if valid_keys >= 64:
5858+ return n
5959+6060+ break
6161+6262+ i += 1
6363+6464+6565+if __name__ == "__main__":
6666+ SALT = fileinput.input()[0].strip()
6767+6868+ print "Index of 64th one-time pad key", find_pad_key_64(SALT, salty_md5)
6969+ print "Index of key-stretched pad key", find_pad_key_64(SALT, stretched_md5)