Lockbox PIN Code Generator

by Victor

Months ago, an associate was commenting on the oddities of a physical key lockbox.  I'm sure you're familiar with the type of lockbox typically used by realtors which are intended to securely store a house key; opened with a PIN code or dial combination lock.  So my associate's "uncle" had "forgotten" the combination or acquired one of these things and was trying to brute force the box.

The lock box in question was of the push-button variety, opening with a numeric PIN.  While the PIN length can vary, he knew that the PIN on his lockbox was four digits long.  Trying up to 10,000 PINs sounds like quite a boring task, right?  But wait, there's more.  The lockbox in question was made by Supra and, after some querying, he learned there were deficiencies in the design of this lockbox that significantly reduced the number of unique PINs.  The PIN couldn't repeat any numbers and the order in which the pin was entered didn't matter (e.g., 1234 was the same as 4321)!

My associate started searching, but couldn't find a ready-made list of PINs.  His initial attempts at generating a list weren't quite right and I was drawn into the idea of solving this with some Python.

I'll give you the executive summary and you can jump straight to the code.  We're generating the PINs as a string, so it can be padded with leading zeroes to the necessary length.  Converting the PIN to a list allows us to sort.  Sorting the PIN's characters is what addresses the fact that the order in which a PIN is entered does not matter.  There's also a check to eliminate PINs which use any digit more than once.

The check to eliminate PINs using any digit more than once might look strange to those less familiar with Python syntax:

if [c for c in pin if pin.count(c) > 1 ]:

This is really a one-liner for creating a list.  See "List Comprehension" in the Python docs.  It iterates the characters in the PIN and returns a list containing only characters that exist more than once in the PIN.  Python's if evaluates to true only when the returned list contains something.

It was reported to me that the resulting list of PINs and a six-pack later, his uncle was triumphant!  I suspect the Supra lockbox model in question was mechanical in nature (as opposed to having some electronic guts), which led to these strange properties.  The number of viable PINs was shockingly low, as you can see below.  What I hadn't thought of is that because PIN order doesn't matter, a five-digit PIN is the most secure - more or less digits reduces security.  Remember that when brute forcing, you're likely to hit on the winner halfway through the key space, so halve those numbers below to get a better idea of just how few tries it's likely to take.

It might be worth taking a minute to tinker and search for vulnerabilities with any lockbox you plan to use.  I suspect those industrious fellows in the Lockpick Village are having a chuckle at this...  I'm certain there are more egregious physical flaws in these types of products.

This is a fine start for PIN-generating needs which I've reused a couple of times already.  Happy hacking and I'd like to give a nod to $@LV@TiON for bringing this puzzle to my attention.

PIN Length      Number of Unique PIN Combinations
1          	10
2          	45
3          	120
4          	210
5          	252
6          	210
7          	120

#!/usr/bin/env python
#
# Create a pin list to crack a supra key box.
#
# Supra key boxes (I am told) have the unique feature of not requiring
# the owner to remember the order in which a pin is entered (!).
# Additionally numbers in the pin can only be used once (e.g. 2234 is
# invalid because 2 appears twice).
#

if __name__ == '__main__':

   pin_length = 5      # <-- Adjust pin-length here

   i = -1
   end_pin = int('9'*pin_length)
   pin_list = []

   while i < end_pin:
       i += 1

       # generate pin with leading 0's; convert it to a list; sort it
       pin = str(i).zfill(pin_length)
       pin = list(pin)
       pin.sort()

       # skip pins with reoccurring chars; pins already in our list
       if [c for c in pin if pin.count(c) > 1]:
           continue
       if pin in pin_list:
           continue

       # add our pin to the master list of valid pins
       pin_list.append(pin)

   # print results
   for pin in pin_list:
       print(''.join(pin))

   print('There are {pincount} combinations.'.format(
           pincount=len(pin_list)))

Code: lockbox.py




# Edited for 3-digit combo
$ python ./lockbox.py
012
013
014
015
016
017
018
019
023
024
025
026
027
028
029
034
035
036
037
038
039
045
046
047
048
049
056
057
058
059
067
068
069
078
079
089
123
124
125
126
127
128
129
134
135
136
137
138
139
145
146
147
148
149
156
157
158
159
167
168
169
178
179
189
234
235
236
237
238
239
245
246
247
248
249
256
257
258
259
267
268
269
278
279
289
345
346
347
348
349
356
357
358
359
367
368
369
378
379
389
456
457
458
459
467
468
469
478
479
489
567
568
569
578
579
589
678
679
689
789
There are 120 combinations.

# Edited for 4-digit combo
$ python ./lockbox.py
0123
0124
0125
0126
0127
0128
0129
0134
0135
0136
0137
0138
0139
0145
0146
0147
0148
0149
0156
0157
0158
0159
0167
0168
0169
0178
0179
0189
0234
0235
0236
0237
0238
0239
0245
0246
0247
0248
0249
0256
0257
0258
0259
0267
0268
0269
0278
0279
0289
0345
0346
0347
0348
0349
0356
0357
0358
0359
0367
0368
0369
0378
0379
0389
0456
0457
0458
0459
0467
0468
0469
0478
0479
0489
0567
0568
0569
0578
0579
0589
0678
0679
0689
0789
1234
1235
1236
1237
1238
1239
1245
1246
1247
1248
1249
1256
1257
1258
1259
1267
1268
1269
1278
1279
1289
1345
1346
1347
1348
1349
1356
1357
1358
1359
1367
1368
1369
1378
1379
1389
1456
1457
1458
1459
1467
1468
1469
1478
1479
1489
1567
1568
1569
1578
1579
1589
1678
1679
1689
1789
2345
2346
2347
2348
2349
2356
2357
2358
2359
2367
2368
2369
2378
2379
2389
2456
2457
2458
2459
2467
2468
2469
2478
2479
2489
2567
2568
2569
2578
2579
2589
2678
2679
2689
2789
3456
3457
3458
3459
3467
3468
3469
3478
3479
3489
3567
3568
3569
3578
3579
3589
3678
3679
3689
3789
4567
4568
4569
4578
4579
4589
4678
4679
4689
4789
5678
5679
5689
5789
6789
There are 210 combinations.
Return to $2600 Index