# Simplex Lock Combinations

## Table of Contents

I have a small safe with a mechanical combination lock called a Simplex 9600. It consists of five buttons and a knob. You enter the combination with the buttons and then twist the knob to open.

Clicking the button below will generate a random combination. Read on to find out how lock combinations work and how to generate all possible combinations.

**Note**: The above button produces any of the possible combinations, but not all of them should actually be used. Some are too simple or too likely to be guessed. Besides the obvious ones that are too simple (e.g. “3”), the factory default of “2+4, 3” should also be avoided.

## How It Works #

At first glance this would seem like a fairly low security lock. Five buttons—each of which may only be used once—means a total of only 120 combinations. At a sedate 5 seconds per combination you could try all of them in ten minutes. While it really is a fairly light duty lock, the number of combinations are actually much higher.

- Combinations can use 1 to 5 of the buttons
- A button may only be used once
- Buttons may be pressed simultaneously

(There’s also a half-press trick that effectively doubles the number of combinations. I’m ignoring it here, though. It’s impractical to use and even more impractical to explain to someone else if you need them to open your safe.)

## Mathematical Preliminaries #

First, some notation and definitions:

- a, b means buttons are pushed in sequence—first a, then b.
- a+b means buttons are pushed simultaneously.
- \(n! = n(n-1)(n-2)\ldots(3)(2)(1)\) is the factorial of n.
- \((n)_{k} = \frac{n!}{(n-k)!}\) is the falling factorial. It counts the number of permutations of length k drawn from a set of n items.
- \({n \choose k} = \frac{n!}{(n - k)!k!}\) is the binomial coefficient. It counts the number of combinations of k elements drawn from a set of n items.

Also briefly, a permutation is a unique order of a set (or subset) of items. For example, \((1, 2, 3)\) and \((3, 2, 1)\) are both permutations of the set \(\lbrace 1, 2, 3\rbrace\). They are also length-three permutations of the set \(\lbrace1, 2, 3, 4, 5\rbrace\).

A combination is a unique subset drawn from a set of items. For example, \(\lbrace 1, 2 \rbrace\) and \(\lbrace 1, 3 \rbrace\) are both combinations of length 2 drawn from the set \(\lbrace 1, 2, 3 \rbrace\). Note that this use of “combination” has a specific mathematical meaning and is different from the more general “lock combination” usage. Which one I mean should be clear from the context.

The key difference between permutations and combinations is **order**.
Permutations define unique *orders*, while combinations define unique
*sets*. So while \((1, 2, 3)\) and \((2, 1, 3)\) are different
permutations, \(\lbrace 1, 2, 3 \rbrace\) and \(\lbrace 2, 1, 3
\rbrace\) are the same set and therefore the same combination.
Permutations have order, combinations do not.

## Number of Lock Combinations #

Before we get to what the combinations are, let’s see if we can just count them. Simplex lock combinations use one of 18 different patterns, listed in the table below. For example, “a+b, c” refers to all combinations with three buttons in total, two of which are pressed simultaneously, and there are \( {5 \choose 2} {3 \choose 1} 2! = 60\) combinations matching that pattern. Also note that the pattern and count include permutations like “a, b+c.”

Pattern | Calculation | Size |
---|---|---|

a | \( (5)_{1} \) | 5 |

a, b | \( (5)_{2} \) | 20 |

a+b | \( 5\choose2 \) | 10 |

a, b, c | \( (5)_{3} \) | 60 |

a+b, c | \( {5\choose2} {3\choose1} 2! \) | 60 |

a+b+c | \( 5\choose3 \) | 10 |

a, b, c, d | \( (5)_{4} \) | 120 |

a+b, c, d | \( {5\choose2} {3\choose2} 3! \) | 180 |

a+b, c+d | \( {5\choose2}{3\choose2} \) | 30 |

a+b+c, d | \( {5\choose3} {2\choose1} 2! \) | 40 |

a+b+c+d | \( {5\choose4} \) | 5 |

a, b, c, d, e | \( 5! \) | 120 |

a+b, c, d, e | \( {5\choose2} 4! \) | 240 |

a+b, c+d, e | \( \frac{1}{2}{5\choose2}{3\choose2} 3! \) | 90 |

a+b+c, d, e | \( {5\choose3} 3! \) | 60 |

a+b+c, d+e | \( {5\choose3}{2\choose2} 2! \) | 20 |

a+b+c+d, e | \( {5\choose4} 2! \) | 10 |

a+b+c+d+e | \( {5\choose5} \) | 1 |

1081 |

There are 1081 possible combinations. At 5 seconds per guess it would take a little over an hour and a half to check them all. That’s still not a huge time investment, but it’s a lot better than 10 minutes. At that point destructive attacks are far more practical.

## Generating All Combinations #

A straightforward, albeit difficult to implement approach, would be to generate each possible pattern above. This works, but it’s a lot of code with a lot of potential mistakes.

There’s an easier way, but it’s not necessarily faster. However, it
doesn’t actually *need* to be faster. The result is never going to
change, so it can be run once and then stored as a table.

```
from itertools import permutations, combinations
def strictly_increasing(p):
"""Determines if p is in strictly increasing order.
This is used to exclude duplicate button groups since
holding 1+2 at the same time is the same as holding 2+1.
"""
if len(p) < 2:
return True
return all(p[i] < p[i + 1] for i in range(len(p) - 1))
def all_groupings(p):
"""Generates all groupings of permutation p."""
if len(p) <= 1:
if strictly_increasing(p):
yield p
return
for i in range(1, len(p) + 1):
if i == 1:
yield from ((p[0],) + q for q in all_groupings(p[1:]))
elif strictly_increasing(p[:i]):
yield from ((p[:i],) + q for q in all_groupings(p[i:]))
def simplex():
"""Generates all simplex lock combinations."""
for i in range(1, 6):
for p in permutations(range(1, 6), i):
yield from all_groupings(p)
```

Finally, the results can be converted to strings for humans, and the whole thing can be dumped to a file. Once you’ve got a list, choosing a random element is easy.

```
def comb_to_str(c):
"""Converts a combination in tuple form to a human-readable string."""
res = []
for x in c:
try:
res.append("+".join(str(n) for n in x))
except TypeError:
res.append(str(x))
return ", ".join(res)
all_combinations = [comb_to_str(c) for c in simplex()]
```