Last active
December 28, 2015 09:09
-
-
Save iandexter/7476990 to your computer and use it in GitHub Desktop.
Generate ``tough nut'' passwords and passphrases by padding and using permutations of common words, then calculate the bit entropy for each generated password. Bonus: XKCD passphrase scheme. <http://blog.iandexter.net/2013/11/tough-nut-to-crack.html>
This file contains hidden or 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
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" | |
Generate ``tough nut'' passwords and passphrases by padding and using | |
permutations of common words, then calculate the bit entropy for each generated | |
password. | |
Usage: python toughnut_gen.py n file1 file2 ... filen | |
n Number of passwords to generate (default: 10) | |
file List of common words to use | |
(default: internal seed list) | |
""" | |
import random | |
import string | |
import sys | |
from math import log | |
### Seed list for passphrases and common words | |
seed = [ | |
'My grace is sufficient for you, for my power is made perfect in weakness.', | |
'A generous man will prosper; he who refreshes others will himself be \ | |
refreshed.', | |
'password', | |
'random', | |
'tough nut', | |
'cracker', | |
'correct horse battery staple', | |
'2 Corinthians 8-9' | |
] | |
### Variables for password generation | |
k = 3 # Random range for padding | |
tmin = 12 # Minimum for random range of password length | |
tmax = 32 # Maximum for random range of password length | |
### Functions | |
def readfiles(filenames): | |
""" Provide seed list for generation """ | |
seed_list = [] | |
if len(filenames) > 0: | |
for f in filenames: | |
lines = open(f, "r").readlines() | |
for l in lines: | |
seed_list.extend(l.split()) | |
else: | |
seed_list.extend(seed) | |
return seed_list | |
def tolower(c): return c.lower() | |
def toupper(c): return c.upper() | |
def camel(word = None): | |
""" Randomly use upper- or lower-case """ | |
o = [] | |
for c in word: | |
random_func = random.choice([tolower, toupper]) | |
o.append(random_func(c)) | |
return ''.join(o) | |
def obfuscate(word = None): | |
""" Transform characters individually """ | |
trans_table = { | |
'a': 'Aa@2', 'c': 'Cc(', 'e': 'Ee3', | |
'g': 'Gg69&', 'h': 'Hh#', 'i': 'Ii1!|', | |
'o': 'Oo0', 's': 'Ss$45', 't': 'Tt7+', | |
'x': 'Xx*' | |
} | |
o = [] | |
for c in word: | |
if c.lower() in trans_table: | |
t = trans_table[c.lower()] | |
o.append(random.choice(t)) | |
else: | |
o.append(camel(c)) | |
return ''.join(o) | |
def pad(r = False, n = 0, s = None): | |
""" Add padding """ | |
p = [] | |
if r: | |
rng = random.randrange(n) | |
else: | |
rng = n | |
for i in range(rng): | |
p.append(random.choice(s)) | |
return ''.join(p) | |
def generate(str = None, k = 0, tmin = 0, tmax = 0): | |
""" Pad words with random characters """ | |
n = [] | |
t = random.randint(tmin,tmax) | |
for s in str.split(): | |
n.append(pad(True, k, string.punctuation)) | |
random_func = random.choice([obfuscate, camel]) | |
n.append(random_func(s)) | |
n.append(pad(True, k, string.ascii_letters)) | |
if len(''.join(n)) < t: | |
d = t - len(''.join(n)) | |
n.append(pad(False, d, string.punctuation + string.digits)) | |
n = ''.join(n) | |
return [n, entropy(n)] | |
def xkcd_gen(word_list = [], wmin = 0, wmax = 0): | |
""" Generate passphrase based on xkcd/936 """ | |
random.shuffle(word_list) | |
nw = random.randrange(wmin,wmax) | |
p = ''.join(w.strip() for w in word_list[:nw]) | |
return [p, xkcd_entropy(len(word_list), nw)] | |
def xkcd_entropy(len_list = 0, num_words = 0): | |
""" Compute entropy for XKCD passphrase """ | |
return num_words * log(len_list, 2) | |
def entropy(str = None): | |
""" Compute bit entropy """ | |
N = 0 | |
if str.isalpha(): | |
if str.islower() or str.isupper(): | |
N += len(string.ascii_lowercase) | |
else: | |
N += len(string.ascii_letters) | |
elif str.isalnum(): | |
N += len(string.ascii_letters + string.digits) | |
else: | |
N += len(string.ascii_letters + string.digits + string.punctuation) | |
return len(str) * log(N,2) | |
### Main | |
if __name__ == '__main__': | |
if len(sys.argv) > 1: | |
r = int(sys.argv[1]) | |
else: | |
r = 10 | |
seed_files = sys.argv[2:] | |
seed_list = readfiles(seed_files) | |
password_list = [] | |
### Plain-text | |
for i in range(int(r/4)): | |
p = random.choice(seed_list) | |
password_list.append([p, entropy(p)]) | |
### Character substitutions | |
for i in range(int(r/4)): | |
s = random.choice(seed_list) | |
if len(s) < tmax: | |
d = random.randint(tmin,tmax) | |
s += pad(False, d, string.digits) | |
password_list.append([s, entropy(s)]) | |
### Character substitutions + padding | |
for i in range(int(r/4)): | |
s = generate(random.choice(seed_list), k, tmin, tmax) | |
password_list.append(s) | |
### XKCD scheme | |
for i in range(int(r/4)): | |
password_list.append(xkcd_gen(seed_list, 3, 5)) | |
p_list = [] | |
for p in password_list: | |
p_list.append(p[0]) | |
max_width = len(max(p_list, key=len)) | |
password_list.sort(key=lambda x: x[1], reverse=True) | |
for p in password_list: | |
print "%-*s %4d %8.2f" % (max_width, p[0], len(p[0]), p[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment