Day 5: How About a Nice Game of Chess?

author: Harshvardhan Pandit

license: MIT

link to problem statement

You are faced with a security door designed by Easter Bunny engineers that seem to have acquired most of their security knowledge by watching hacking movies).

The eight-character password for the door is generated one character at a time by finding the MD5 hash of some Door ID (your puzzle input) and an increasing integer index (starting with 0).

A hash indicates the next character in the password if its hexadecimal representation starts with five zeroes. If it does, the sixth character in the hash is the next character of the password.

For example, if the Door ID is abc:

- The first index which produces a hash that starts with five zeroes is `3231929`, which we find by hashing `abc3231929`; the sixth character of the hash, and thus the first character of the password, is `1`.
- `5017308` produces the next interesting hash, which starts with `000008f82`..., so the second character of the password is `8`.
- The third time a hash starts with five zeroes is for `abc5278568`, discovering the character f.

In this example, after continuing this search a total of eight times, the password is 18f47a30.

Given the actual Door ID, what is the password?

Solution logic

The password is eight characters long, so any loop or condition we run needs to have that many iterations. Python has a handy way to calculate the MD5 hash using hashlib module.

import hashlib
md5 = hashlib.md5()
md5.update('string-here')
md5.hexdigest()

Next, we need to keep track of the integers we are suffixing the door ID with. Starting from 0, without any upper limit. Once the MD5 is calculated, it is of interest only when it starts with 5 zeroes. So we check if the string starts with '00000'

md5.startswith('00000')

If it does, we append the sixth character as the password.

Algorithm

- set password to empty string
- set hash_suffix to 0
- while password length is not 6:
    - append hash_suffix to door ID
    - take MD5
    - check if it starts with '00000'
            - if it does, append sixth character to password
    - increment hash_suffix by 1

Input

The input for this puzzle is a single line, but I still store it in an input file so as to make it available to any other scritps written in the future.


In [1]:
with open('../inputs/day05.txt', 'r') as f:
    door_id = f.readline().strip()

Initialising variables, importing libraries


In [2]:
import hashlib
password = ''
hash_suffix = 0

In [3]:
while len(password) != 8:
    string = door_id + str(hash_suffix)
    md5 = hashlib.md5()
    md5.update(string.encode('ascii'))
    md5hash = md5.hexdigest()
    if md5hash.startswith('00000'):
        password += md5hash[5]
    hash_suffix += 1

Part Two

As the door slides open, you are presented with a second door that uses a slightly more inspired security mechanism. Clearly unimpressed by the last version (in what movie is the password decrypted in order?!), the Easter Bunny engineers have worked out a better solution.

Instead of simply filling in the password from left to right, the hash now also indicates the position within the password to fill. You still look for hashes that begin with five zeroes; however, now, the sixth character represents the position (0-7), and the seventh character is the character to put in that position.

A hash result of 000001f means that f is the second character in the password. Use only the first result for each position, and ignore invalid positions.

For example, if the Door ID is abc:

- The first interesting hash is from `abc3231929`, which produces `0000015`...; so, `5` goes in position `1`: `_5______`.
- In the previous method, `5017308` produced an interesting hash; however, it is ignored, because it specifies an invalid position `(8)`.
- The second interesting hash is at index `5357525`, which produces `000004e`...; so, e goes in position `4`: `_5__e___`.

You almost choke on your popcorn as the final character falls into place, producing the password 05ace8e3.

Given the actual Door ID and this new method, what is the password? Be extra proud of your solution if it uses a cinematic "decrypting" animation.

Solution logic

Not much has changed from the last puzzle. We still calculate the MD5 hash as before. However, we now have an extra condition to check whether the hash is valid. The sixth character now represents the position of the character in the password. The seventh character is the actual password character. So we need some way to store password characters with positions. An easy approach would be a list pre-filled with eight None values. This makes it trivial to store the password characters by index.

password = [None for i in range(0, 8)]

To check whether the position parameter is valid, we need to ensure that it is in the range 0..7 (8 total).

'0' <= ch <= '7'

And also whether the character at that position has already been filled

password[ch -'0'] is not None

The ch - '0' converts the ascii digit to its numerical value. Another way to do that is to use int(ch).

Instead of using length of the list, we now explicitly keep a track of password character count, since len(password) will always be 8.


In [4]:
password_char_count = 0
password = [None for i in range(0, 8)]
hash_suffix = 0

In [5]:
while password_char_count != 8:
    string = door_id + str(hash_suffix)
    hash_suffix += 1
    md5 = hashlib.md5()
    md5.update(string.encode('ascii'))
    md5hash = md5.hexdigest()
    if not md5hash.startswith('00000'):
        continue
    if not md5hash[5].isdigit():
        continue
    position_char = int(md5hash[5])
    if 0 <= position_char <= 7 and password[position_char] is None:
        password[position_char] = md5hash[6]
        password_char_count += 1
''.join(password)


Out[5]:
'424a0197'

== END ==