Skip to content

Instantly share code, notes, and snippets.

@GrantTrebbin
Created May 19, 2017 10:23
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
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.
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='#')
@GrantTrebbin
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment