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.