Created
May 19, 2017 10:23
-
-
Save GrantTrebbin/c3997bb2f07f897af25156e06ba0675c to your computer and use it in GitHub Desktop.
Generate sequences to brute force locks with mechaincal pin codes.
Works for locks where order of numbers in PIN doesn't matter and numbers can't be repeated.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from itertools import combinations, chain | |
# https://stackoverflow.com/questions/5920643/add-an-item-between-each-item-already-in-the-list | |
def intersperse(lst, item): | |
result = [item] * (len(lst) * 2) | |
result[0::2] = lst | |
return result | |
pins = [] | |
number_of_digits = 10 | |
# Generate all the possible PIN combinations for each PIN length | |
for length in range(number_of_digits + 1): | |
new_pins = [list(pin) for pin in list(combinations(range(number_of_digits), length))] | |
# ensure that each pin is in order from smallest number to largest | |
for pin in new_pins: | |
pin.sort() | |
# sort the pins element by element (lexicographically) | |
new_pins.sort() | |
pins.append(new_pins) | |
# Start with all the pins of length half the number of buttons | |
sequences = [[None, pin, None] for pin in pins[int(number_of_digits/2)]] | |
# Add pins one shorter and one longer to the sequence of pins | |
# Place them in the first logical spot found | |
for offset in range(1, int(number_of_digits/2+1)): | |
for short_pin in pins[int(number_of_digits/2) - offset]: | |
for pin in sequences: | |
difference = set(pin[1]) - set(short_pin) | |
number_of_different_digits = len(difference) | |
if (number_of_different_digits == 1) and (pin[0] is None): | |
pin[0] = short_pin | |
break | |
for long_pin in pins[int(number_of_digits/2) + offset]: | |
for pin in sequences: | |
difference = set(long_pin) - set(pin[-2]) | |
number_of_different_digits = len(difference) | |
if (number_of_different_digits == 1) and (pin[-1] is None): | |
pin[-1] = long_pin | |
break | |
# Make sure each sequence starts and ends with a None to allow the | |
# iteration to work. | |
for sequence in sequences: | |
if sequence[0] is not None: | |
sequence.insert(0, None) | |
if sequence[-1] is not None: | |
sequence.append(None) | |
# Trim the None values from the start and end and then pad to the correct length | |
for sequence in sequences: | |
if sequence[0] is None: | |
sequence.pop(0) | |
if sequence[-1] is None: | |
sequence.pop(-1) | |
# Sort the sequences by the number of pins they cover | |
sequences.sort(key=lambda x: -len(x)) | |
aligned_sequences = [] | |
for sequence in sequences: | |
# Find the next button to press in each sequence | |
next_digit_lists = [list(set(j) - set(i)) for i, j in zip(sequence[:-1], | |
sequence[1:])] | |
# Merge all the button presses together | |
next_digits = list(chain(*next_digit_lists)) | |
# Line up the pins for presentation | |
new_sequence = [""] * (number_of_digits + 1) | |
for pin in sequence: | |
new_sequence[len(pin)] = pin | |
# <> represents resetting the lock and _ represents testing if it will open | |
# The output will be generated as a hash separated file for easy import into | |
# a spreadsheet | |
setup = ['<> '] | |
setup.extend(intersperse(sequence[0], " ")) | |
setup.append('_') | |
setup.extend(intersperse(next_digits, "_")) | |
button_sequence = ''.join(map(str, setup)) | |
new_sequence = [button_sequence] + new_sequence | |
aligned_sequences.append(new_sequence) | |
# Print the sequences | |
for sequence in aligned_sequences: | |
pass | |
print(*sequence, sep='#') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
More details at http://www.grant-trebbin.com/2017/05/testing-all-pins-on-lock-box-with.html