From 03b3db5e9de177a75d964982c4ee28a707b0bce1 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Thu, 25 Apr 2019 15:39:56 +0200 Subject: [PATCH 1/2] Add option -D/--delimiter-special. --- CHANGES.rst | 5 ++++- README.rst | 5 +++++ diceware/__init__.py | 13 ++++++++++++- diceware/config.py | 1 + tests/sample_dot_diceware.ini | 1 + 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4bbc32f..2e1c1f4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,10 @@ Changes 0.9.7.dev0 (yet unreleased) --------------------------- -(no changes yet) +- Added option ``--delimiter-special``. Thanks to `Hartmut Goebel + `_. +- Added German, Italien, Latin and two Spanish wordlists. Thanks to + `Hartmut Goebel `_. 0.9.6 (2018-12-19) ------------------ diff --git a/README.rst b/README.rst index e11cd06..c38ff30 100644 --- a/README.rst +++ b/README.rst @@ -73,6 +73,8 @@ Once installed, use ``--help`` to list all available options:: Insert NUM special chars into generated word. -d DELIMITER, --delimiter DELIMITER Separate words by DELIMITER. Empty string by default. + -D NUM, --delimiter-special NUM + Separate words by NUM special chars (none by default). -r SOURCE, --randomsource SOURCE Get randomness from this source. Possible values: `realdice', `system'. Default: system @@ -208,6 +210,7 @@ directory. This file could look like this:: caps = off specials = 2 delimiter = "MYDELIMITER" + delimiter_special = 0 randomsource = "system" wordlist = "en_securedrop" @@ -412,6 +415,8 @@ People that helped spotting bugs, providing solutions, etc.: - `Simon Fondrie-Teitler `_ contributed a machine-readable copyright file, with improvements from `@anarcat`_ - `Doug Muth `_ fixed formatting in docs. + - `Hartmut Goebel `_ added option + ``--delimiter-special`` and some wordlists. Many thanks to all of them! diff --git a/diceware/__init__.py b/diceware/__init__.py index d034c43..e9002b1 100644 --- a/diceware/__init__.py +++ b/diceware/__init__.py @@ -107,6 +107,9 @@ def handle_options(args): parser.add_argument( '-d', '--delimiter', default='', help="Separate words by DELIMITER. Empty string by default.") + parser.add_argument( + '-D', '--delimiter-special', default=0, type=int, metavar='NUM', + help="Separate words by NUM special chars (none by default).") parser.add_argument( '-r', '--randomsource', default='system', choices=random_sources, metavar="SOURCE", @@ -190,7 +193,15 @@ def get_passphrase(options=None): words = [rnd.choice(list(word_list)) for x in range(options.num)] if options.caps: words = [x.capitalize() for x in words] - result = options.delimiter.join(words) + if options.delimiter_special: + lengths = list(range(1, options.delimiter_special + 1)) + for pos in range(len(words)-1, 0, -1): + l = rnd.choice(lengths) + deli = "".join(rnd.choice(SPECIAL_CHARS) for j in range(l)) + words.insert(pos, deli) + result = "".join(words) + else: + result = options.delimiter.join(words) for _ in range(options.specials): result = insert_special_char(result, rnd=rnd) return result diff --git a/diceware/config.py b/diceware/config.py index ae1f7b9..c07078e 100644 --- a/diceware/config.py +++ b/diceware/config.py @@ -31,6 +31,7 @@ caps=True, specials=0, delimiter="", + delimiter_special=0, randomsource="system", verbose=0, wordlist="en_eff", diff --git a/tests/sample_dot_diceware.ini b/tests/sample_dot_diceware.ini index 01e430c..1c86ef0 100644 --- a/tests/sample_dot_diceware.ini +++ b/tests/sample_dot_diceware.ini @@ -3,6 +3,7 @@ num = 6 caps = on specials = 0 delimiter = "" +delimiter_special = 0 randomsource = "system" verbose = 0 wordlist = "en_securedrop" From 7b621f9d2528b249e9a67c23054756c3f7bc2b2b Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Mon, 29 Apr 2019 12:02:07 +0200 Subject: [PATCH 2/2] Add missing test-cases for option -D/--delimiter-special. Also move insert_special_delimiter into a function so it can be tested. --- diceware/__init__.py | 24 +++++++++++++++++++----- tests/test_diceware.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/diceware/__init__.py b/diceware/__init__.py index e9002b1..685e880 100644 --- a/diceware/__init__.py +++ b/diceware/__init__.py @@ -163,6 +163,24 @@ def insert_special_char(word, specials=SPECIAL_CHARS, rnd=None): char_list[rnd.choice(range(len(char_list)))] = rnd.choice(specials) return ''.join(char_list) +def insert_special_delimiter(words, max_delimiter_chars, + 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() + words = words[:] + lengths = list(range(1, max_delimiter_chars + 1)) + for pos in range(len(words)-1, 0, -1): + num_chars = rnd.choice(lengths) # choose number of chars to insert + deli = "".join(rnd.choice(specials) for j in range(num_chars)) + words.insert(pos, deli) + return words def get_passphrase(options=None): """Get a diceware passphrase. @@ -194,11 +212,7 @@ def get_passphrase(options=None): if options.caps: words = [x.capitalize() for x in words] if options.delimiter_special: - lengths = list(range(1, options.delimiter_special + 1)) - for pos in range(len(words)-1, 0, -1): - l = rnd.choice(lengths) - deli = "".join(rnd.choice(SPECIAL_CHARS) for j in range(l)) - words.insert(pos, deli) + words = insert_special_delimiter(words, options.delimiter_special) result = "".join(words) else: result = options.delimiter.join(words) diff --git a/tests/test_diceware.py b/tests/test_diceware.py index 319e919..1299bfe 100644 --- a/tests/test_diceware.py +++ b/tests/test_diceware.py @@ -9,7 +9,7 @@ from diceware import ( get_wordlists_dir, SPECIAL_CHARS, insert_special_char, get_passphrase, handle_options, main, __version__, print_version, get_random_sources, - get_wordlist_names + get_wordlist_names, insert_special_delimiter ) @@ -86,6 +86,15 @@ def test_handle_options_delimiter(self): options = handle_options(['-d', 'WOW']) assert options.delimiter == 'WOW' + def test_handle_options_delimiter_special(self): + # we can set number of special characters to be used as delimiter + options = handle_options([]) + assert options.delimiter_special == 0 + options = handle_options(['-D', '3']) + assert options.delimiter_special == 3 + options = handle_options(['--delimiter-special', '1']) + assert options.delimiter_special == 1 + def test_handle_options_randomsource(self): # we can choose the source of randomness source_names = get_random_sources().keys() @@ -244,6 +253,37 @@ def test_get_passphrase_delimiters(self): phrase = get_passphrase(options) assert " " in phrase + def test_get_passphrase_special_delimiter(self): + # delimiter_special overrules delemiter + options = handle_options(args=[]) + options.delimiter = " " + options.delimiter_special = 2 + phrase = get_passphrase(options) + assert " " not in phrase + + def test_insert_special_delimiter(self): + # we can insert special chars between the words. + fake_rnd = FakeRandom() + fake_rnd.nums_to_draw = [1, 2, 1, # (num of chars)-1, char-idx + 0, 1, + 2, 1, 2, 0] + words_in = ['aaa', 'bbb', 'ccc', 'ddd'] + result1 = insert_special_delimiter(words_in, 3, + specials='!$&', rnd=fake_rnd) + assert result1 == ['aaa', '$&!', 'bbb', '$', 'ccc', '&$', 'ddd'] + assert words_in == ['aaa', 'bbb', 'ccc', 'ddd'] # unchanges + + def test_insert_special_delimiter_defaults(self): + # defaults are respected + words_in = ['aaa', 'bbb'] + result1 = insert_special_delimiter(words_in, 2) + assert result1[0] == 'aaa' + assert result1[2] == 'bbb' + assert 1 <= len(result1[1]) <= 2 + assert result1[1][0] in SPECIAL_CHARS + if len(result1[1]) == 2: + result1[1][1] in SPECIAL_CHARS + def test_print_version(self, capsys): # we can print version infos print_version()