You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
71 lines
2.7 KiB
71 lines
2.7 KiB
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import argparse, math, random
|
|
|
|
from pyutil.mathutil import div_ceil
|
|
|
|
from pkg_resources import resource_stream
|
|
|
|
def recursive_subset_sum(entropy_needed, wordlists):
|
|
# Pick a minimalish set of numbers which sum to at least
|
|
# entropy_needed.
|
|
|
|
# Okay now what's the smallest number of words which will give us
|
|
# at least this much entropy?
|
|
entropy_of_biggest_wordlist = wordlists[-1][0]
|
|
assert isinstance(entropy_of_biggest_wordlist, float), wordlists[-1]
|
|
needed_words = div_ceil(entropy_needed, entropy_of_biggest_wordlist)
|
|
# How much entropy do we need from each word?
|
|
needed_entropy_per_word = entropy_needed / needed_words
|
|
# What's the smallest wordlist that offers at least this much
|
|
# entropy per word?
|
|
for (wlentropy, wl) in wordlists:
|
|
if wlentropy >= needed_entropy_per_word:
|
|
break
|
|
assert wlentropy >= needed_entropy_per_word, (wlentropy, needed_entropy_per_word)
|
|
|
|
result = [(wlentropy, wl)]
|
|
# If we need more, recurse...
|
|
if wlentropy < entropy_needed:
|
|
rest = recursive_subset_sum(entropy_needed - wlentropy, wordlists)
|
|
result.extend(rest)
|
|
return result
|
|
|
|
def gen_passphrase(entropy, allwords):
|
|
maxlenwords = []
|
|
i = 2 # The smallest set is words of length 1 or 2.
|
|
words = [x for x in allwords if len(x) <= i]
|
|
maxlenwords.append((math.log(len(words), 2), words))
|
|
while len(maxlenwords[-1][1]) < len(allwords):
|
|
i += 1
|
|
words = [x for x in allwords if len(x) <= i]
|
|
maxlenwords.append((math.log(len(words), 2), words))
|
|
|
|
sr = random.SystemRandom()
|
|
passphrase = []
|
|
|
|
wordlists_to_use = recursive_subset_sum(entropy, maxlenwords)
|
|
|
|
passphraseentropy = 0.0
|
|
for (wle, wl) in wordlists_to_use:
|
|
passphrase.append(sr.choice(wl))
|
|
passphraseentropy += wle
|
|
|
|
return (u".".join(passphrase), passphraseentropy)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(prog="chbs", description="Create a random passphrase by picking a few random words.")
|
|
|
|
parser.add_argument('-d', '--dictionary', help="what file to read a list of words from (or omit this option to use chbs's bundled dictionary)", type=argparse.FileType('rU'), metavar="DICT")
|
|
parser.add_argument('bits', help="how many bits of entropy minimum", type=float, metavar="BITS")
|
|
args = parser.parse_args()
|
|
|
|
dicti = args.dictionary
|
|
if not dicti:
|
|
dicti = resource_stream('pyutil', 'data/wordlist.txt')
|
|
allwords = set([x.decode('utf-8').strip().lower() for x in dicti.readlines()])
|
|
|
|
passphrase, bits = gen_passphrase(args.bits, allwords)
|
|
|
|
print u"Your new password is: '%s'. It is worth about %s bits." % (passphrase, bits)
|
|
|