Skip to content
Snippets Groups Projects
diceware.py 3.42 KiB
Newer Older
  • Learn to ignore specific revisions
  • ulif's avatar
    ulif committed
    import argparse
    
    ulif's avatar
    ulif committed
    import os
    
    ulif's avatar
    ulif committed
    import re
    
    ulif's avatar
    ulif committed
    import sys
    
    from random import SystemRandom
    
    
    ulif's avatar
    ulif committed
    
    #: The directory in which wordlists are stored
    SRC_DIR = os.path.dirname(__file__)
    
    
    ulif's avatar
    ulif committed
    #: A regular expression matching 2 consecutive ASCII chars. We
    #: consider this to represent some language/country code.
    RE_LANG_CODE = re.compile('^[a-zA-Z]{2}$')
    
    #: Special chars inserted on demand
    SPECIAL_CHARS = "~!#$%^&*()-=+[]\{}:;\"'<>?/0123456789"
    
    ulif's avatar
    ulif committed
    
    
    ulif's avatar
    ulif committed
    
    
    ulif's avatar
    ulif committed
    def handle_options(args):
        """Handle commandline options.
        """
        parser = argparse.ArgumentParser(description="Create a passphrase")
    
    ulif's avatar
    ulif committed
        parser.add_argument(
            '-n', '--num', default=6, type=int,
            help='number of words to concatenate. Default: 6')
    
    ulif's avatar
    ulif committed
        cap_group = parser.add_mutually_exclusive_group()
        cap_group.add_argument(
    
    ulif's avatar
    ulif committed
            '-c', '--caps', action='store_true',
    
    ulif's avatar
    ulif committed
            help='Capitalize words. This is the default.')
        cap_group.add_argument(
    
    ulif's avatar
    ulif committed
            '--no-caps', action='store_false', dest='capitalize',
    
    ulif's avatar
    ulif committed
            help='Turn off capitalization.')
    
    ulif's avatar
    ulif committed
        parser.add_argument(
    
    ulif's avatar
    ulif committed
            '-s', '--specials', default=0, type=int, metavar='NUM',
    
    ulif's avatar
    ulif committed
            help="Insert NUM special chars into generated word.")
    
    ulif's avatar
    ulif committed
        parser.set_defaults(capitalize=True)
    
    ulif's avatar
    ulif committed
        args = parser.parse_args(args)
        return args
    
    
    
    ulif's avatar
    ulif committed
    def get_wordlist(path):
        """Parse file at `path` and build a word list of it.
    
    ulif's avatar
    ulif committed
    
    
    ulif's avatar
    ulif committed
        A wordlist is expected to contain lines of words. Each line a
        word. Empty lines are ignored. Returns a list of terms (lines)
        found.
    
    ulif's avatar
    ulif committed
        """
        result = []
        with open(path, 'r') as fd:
    
    ulif's avatar
    ulif committed
            result = [line.strip() for line in fd.readlines()
                      if line.strip() != '']
    
    ulif's avatar
    ulif committed
        return result
    
    
    ulif's avatar
    ulif committed
    def get_wordlist_path(lang):
        """Get path to a wordlist file for language `lang`.
    
        The `lang` string is a 2-char country code. Invalid codes raise a
        ValueError.
        """
    
    ulif's avatar
    ulif committed
        if not RE_LANG_CODE.match(lang):
    
    ulif's avatar
    ulif committed
            raise ValueError("Not a valid language code: %s" % lang)
    
    ulif's avatar
    ulif committed
        basename = 'wordlist_%s.txt' % lang
    
    ulif's avatar
    ulif committed
        return os.path.abspath(os.path.join(
            SRC_DIR, 'wordlists', basename.lower()))
    
    ulif's avatar
    ulif committed
    def insert_special_char(word, specials=SPECIAL_CHARS, rnd=None):
        """Insert a char out of `specials` into `word`.
    
        `rnd`, if passed in, will be used as a (pseudo) random number
        generator. We use `.choice()` only.
    
        Returns the modified word.
        """
        if rnd is None:
            rnd = SystemRandom()
        char_list = list(word)
        char_list[rnd.choice(range(len(char_list)))] = rnd.choice(specials)
        return ''.join(char_list)
    
    
    
    ulif's avatar
    ulif committed
    def get_passphrase(wordnum=6, specialsnum=1, delimiter='', lang='en',
    
                       capitalized=True):
        """Get a diceware passphrase.
    
    ulif's avatar
    ulif committed
    
        The passphrase returned will contain `wordnum` words deliimted by
        `delimiter`.
    
        If `capitalized` is ``True``, all words will be capitalized.
    
        The wordlist to pick words from is determined by `lang`,
        representing a language.
    
        """
        word_list = get_wordlist(get_wordlist_path(lang))
        rnd = SystemRandom()
        words = [rnd.choice(word_list) for x in range(wordnum)]
        if capitalized:
            words = [x.capitalize() for x in words]
    
    ulif's avatar
    ulif committed
        result = delimiter.join(words)
    
    ulif's avatar
    ulif committed
        for x in range(specialsnum):
    
    ulif's avatar
    ulif committed
            result = insert_special_char(result, rnd=rnd)
    
    ulif's avatar
    ulif committed
    def main(args=1):
        if args is 1:
            args = sys.argv[1:]
        options = handle_options(args)
    
    ulif's avatar
    ulif committed
        print(get_passphrase(
            wordnum=options.num,
            specialsnum=options.specials,
            capitalized=options.capitalize
            )
        )