Blah blah blah.
Blah blah blah.
seed = 'foo' md5('foo0') → a1b2c3d4 md5('foo1') → 9a8b7c6d md5('foo2') → ff0ee1dd md5('foo3') → a1bbb3d4 # Triple b! md5('foo4') → 987abc65 md5('foo5') → a32cac67 md5('foo6') → 4e60f111 ... md5('foo241') → 3abbbbb0 # 3 is a key!
seed = 'foo' md5('foo0') → a1b2c3d4 md5('foo1') → 9a8b7c6d md5('foo2') → ff0ee1dd md5('foo3') → a1bbb3d4 ✓ md5('foo4') → 987abc65 md5('foo5') → a32cac67 md5('foo6') → 4e60f111 # Triple 1! md5('foo7') → c1bd8e78 ... md5('foo1006') → f5e02b34 # 6 is not a key....
seed = 'foo' md5('foo0') → a1b2c3d4 md5('foo1') → 9a8b7c6d md5('foo2') → ff0ee1dd md5('foo3') → a1bbb3d4 ✓ md5('foo4') → 987abc65 md5('foo5') → a32cac67 md5('foo6') → 4e60f111 ✗ md5('foo7') → c1bd8e78 md5('foo8') → d8a9112f ... # until we find the 64th key
def salted_hash(salt, i): bytes = f"{salt}{i}".encode("ascii") return hashlib.md5(bytes).hexdigest()
>>> salted_hash("something", 17) '594a263646d6b3c5db804b28f62cbc67'
def triple(s): # Return a triple character, or None. m = re.search(r"(.)\1\1", s) if m: return m.group(1)
>>> triple("what aaa something") 'a' >>> triple("hello world") >>> triple("111 22222 333") '1'
def is_key(salt, candidate): # Is `candidate` a key? h = salted_hash(salt, candidate) t = triple(h) if t is None: return False # We have a triple. Look ahead for a quint. for check_ahead in range(1, 1001): h2 = salted_hash(salt, candidate + check_ahead) if t*5 in h2: return True return False
def nth_key(salt, n): # Find the `n`th key starting with `salt`. num_keys = 0 for candidate in itertools.count(): if is_key(salt, candidate): num_keys += 1 if num_keys == n: return candidate
INPUT = 'zpqevtbw' # Yours will be different! k64 = nth_key(INPUT, 64) print(f"Part 1: the 64th key is at index {k64}")
$ python part1.py Part 1: the 64th key is at index 16106
def test_triple(): assert triple("helloaaathere") == "a"
$ pytest part1.py ================ test session starts ================== platform darwin -- Python 3.6.5, pytest-4.0.1, py-1.7.0 rootdir: /Users/ned/prz/adventures, inifile: collected 1 item part1.py . [100%] ============== 1 passed in 0.01 seconds ===============
@pytest.mark.parametrize("s, t", [ ("hello there all", None), ("aaa", "a"), ("0123345xxx112315zzz124xx", "x"), ]) def test_triples(s, t): assert triple(s) == t
$ pytest part1.py ================= test session starts ================= platform darwin -- Python 3.6.5, pytest-4.0.1, py-1.7.0, pluggy-0.8.0 rootdir: /Users/ned/prz/adventures, inifile: collected 4 items part1.py .... [100%] ============== 4 passed in 0.01 seconds ===============
@pytest.mark.parametrize("salt, num, result", [ ('abc', 17, False), # no triple ('abc', 18, False), # triple, no quint ('abc', 39, True), # a key ('abc', 92, True), # a key ('abc', 22727, False), # near a key ('abc', 22728, True), # a key ('abc', 22729, False), # near a key ]) def test_is_key(salt, num, result): assert is_key(salt, num) == result
def test_nth_key(): assert nth_key("abc", 64) == 22728
collected 12 items part1.py ............ [100%]
$ pytest part1.py ================= test session starts ================= collected 12 items part1.py ..F......... [100%] ====================== FAILURES ======================= _________________ test_triples[aaa-b] _________________ s = 'aaa', t = 'b' @pytest.mark.parametrize("s, t", [ ("hello there all", None), ("aaa", "b"), ("0123345xxx112315zzz124xx", "x"), ]) def test_triples(s, t): > assert triple(s) == t E AssertionError: assert 'a' == 'b' E - a E + b part1.py:28: AssertionError ========= 1 failed, 11 passed in 3.80 seconds =========
$ time python part1.py Part 1: the 64th key is at index 16106 real 0m2.945s
def salted_hash_2017(salt, i): val = f"{salt}{i}" for _ in range(2017): val = hashlib.md5(val.encode("ascii")).hexdigest() return val
$ time python naive_part2.py Part 2: the 64th key is at index 22423 real 86m33.516s
HASHES = 0 def salted_hash(salt, i): global HASHES HASHES += 1 bytes = f"{salt}{i}".encode("ascii") return hashlib.md5(bytes).hexdigest()
$ python part1_counted.py Part 1: the 64th key is at index 16106 Total of 1,661,356 hashes
CACHE = {} def salted_hash_2017(salt, i): val = val0 = f"{salt}{i}" if val0 in CACHE: return CACHE[val0] for _ in range(2017): val = hashlib.md5(val.encode("ascii")).hexdigest() CACHE[val0] = val return val
$ time python gcache.py Part 2: the 64th key is at index 22423 real 0m54.688s
def make_cached_hasher(iterations): cache = {} def hasher(salt, i): val = val0 = f"{salt}{i}" if val0 in cache: return cache[val0] for _ in range(iterations): val = hashlib.md5(val.encode("ascii")).hexdigest() cache[val0] = val return val return hasher salted_hash_2017 = make_cached_hasher(2017)
def cached(func): cache = {} def wrapped(*args): if args in cache: return cache[args] ret = func(*args) cache[args] = ret return ret return wrapped @cached def salted_hash_2017(salt, i): # from naive_part2.py val = f"{salt}{i}" for _ in range(2017): val = hashlib.md5(val.encode("ascii")).hexdigest() return val
>>> nums = [1, 2, 3] >>> for num in nums: ... print(num) 1 2 3
>>> num_iter = iter(nums) >>> next(num_iter) 1 >>> next(num_iter) 2 >>> next(num_iter) 3
>>> def countdown(num): ... while num >= 0: ... yield num ... num -= 1 >>> for n in countdown(3): ... print(n) 3 2 1 0
def hashes_2017(salt): for i in itertools.count(): yield salted_hash_2017(salt, i) def test_hashes_2017(): them = hashes_2017("abc") assert next(them) == 'a107ff634856bb300138cac6568c0f24' assert next(them) == '65490b7e1ceeff8ade55d803c02bf553' assert next(them) == 'dff53117a11213b0e5c4cf63fe3c5198'
md5('foo0') → a1b2c3d4 md5('foo1') → 9a8b7c6d md5('foo2') → ff0ee1dd md5('foo3') → a1bbb3d4 # Triple b! md5('foo4') → 987abc65 md5('foo5') → a32cac67 md5('foo6') → 4e60f111 md5('foo7') → c1bd8e78 md5('foo8') → b2b9a2e9 ... md5('foo4') → 987abc65 md5('foo5') → a32cac67 md5('foo6') → 4e60f111 # Triple 1! md5('foo7') → c1bd8e78 ...
def test_peekable(): p = PeekableIterable(itertools.count()) pi = iter(p) assert next(pi) == 0 assert next(pi) == 1 assert p.peek_ahead(1) == 2 assert p.peek_ahead(100) == 101 assert next(pi) == 2 assert p.peek_ahead(1) == 3
class PeekableIterable: """Peek ahead into infinite iterables""" def __init__(self, source): self.source = iter(source) self.lookahead = collections.deque() def __iter__(self): while True: if self.lookahead: yield self.lookahead.popleft() else: yield next(self.source) def peek_ahead(self, index): assert index > 0 while index > len(self.lookahead): self.lookahead.append(next(self.source)) return self.lookahead[index-1]
def key_indexes(salt): """Produce all key indexes from `salt`.""" p = PeekableIterable(hashes_2017(salt)) for index, hash in enumerate(p): t = triple(hash) if t: for i in range(1, 1001): ahead = p.peek_ahead(i) if t*5 in ahead: yield index def test_key_indexes(): ki = key_indexes("abc") assert next(ki) == 10
def n_keys(salt, n): all_keys = key_indexes(salt) return list(itertools.islice(all_keys, n)) if __name__ == "__main__": INPUT = 'zpqevtbw' # Yours will be different! first_64 = n_keys(INPUT, 64) k64 = first_64[-1] print(f"Part 2: the 64th key is at index {k64}")