From f2fa1dc898db84e95caa1d13640f57577539343f Mon Sep 17 00:00:00 2001 From: HacKan Date: Fri, 22 Sep 2017 05:28:17 -0300 Subject: [PATCH] Add calculation for password entropy Remove default password length, calculate it instead based on ENTROPY_BITS_MIN. Warn when password is too short. --- README.md | 12 +++++++----- man/passphrase.1 | 2 +- man/passphrase.md | 4 ++-- src/passphrase.py | 38 ++++++++++++++++++++++++-------------- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 885d774..66096ea 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,19 @@ It also makes use of the [EFF Large Wordlist](https://www.eff.org/es/document/pa A secure passphrase must be of at least 6 words, but 7 is better, and maybe you can add a random number to the list. If you need a password, make it bigger than 8 characters (NIST's latest recommendation), and preffer more than 12 (I recommend 16 or more). Passwords are comprised of digits, upper and lower case letters and punctuation symbols - more specifically: `ascii_letters`, `digits` and `punctuation` from [Lib/string](https://docs.python.org/3.6/library/string.html#string-constants) -. -If you specify a list different than the EFF Large Wordlist, the minimum amount of words for a passphrase to be secure changes: for shorter lists, the amount increases. **Passphrase** calculates the minimum secure amount of words automatically and warns if the chosen number is low. +If you specify a list different than the EFF Large Wordlist, the minimum amount of words for a passphrase to be secure changes: for shorter lists, the amount increases. **Passphrase** calculates the minimum secure amount of words automatically and warns if the chosen number is low. It does the same for a password if it is too short. ## Requirements For **Python 3.6+**: +* numpy 1.13+ [optional] for faster entropy computation * flake8 [optional] for linting For **Python 3.2+**: * LibNaCl 1.5+ +* numpy 1.13+ [optional] for faster entropy computation * flake8 [optional] for linting [passphrase.py](/src/passphrase.py) is a stand-alone, self contained script (the word list is embedded in it). It detects whether you have Python 3.6+ or lower, and acts accordingly. For Python 3.6+, it uses `Lib/secrets` (and is preferred); for Python 3.2+, `libnacl.randombytes_uniform`. @@ -63,10 +65,10 @@ E`31nDL0^$oYu5=' #### Use an external wordlist to generate a passphrase ``` -:~$ passphrase -i my-wordlist.txt -anguished estate placard deceptive entity -:~$ passphrase -d -i my-dicewarelike-wordlist.txt -unnamed unmanned appendix fineness riverside +:~$ passphrase -i eff_short_wordlist_1_1column.txt +wimp broke dash pasta zebra viral outer clasp +:~$ passphrase -d -i eff_short_wordlist_1.txt +mouse trend coach stain shut rhyme baggy scale ``` #### Save the output to a file diff --git a/man/passphrase.1 b/man/passphrase.1 index 6651aad..f2f1147 100644 --- a/man/passphrase.1 +++ b/man/passphrase.1 @@ -12,7 +12,7 @@ password, and prints it to standard output. By default, it uses an embedded EFF Large Wordlist for passphrases. Passphrases with less than 6 words are considered insecure. A safe bet is between 6 and 7 words, plus at least a number. -For passwords, use at least 8 characters, but prefer 12 or more. +For passwords, use at least 12 characters, but prefer 16 or more. .PP Instead of words and numbers, a password (random string of printable characters from Python String standard) can be generated by diff --git a/man/passphrase.md b/man/passphrase.md index 4a92297..14fdb3b 100644 --- a/man/passphrase.md +++ b/man/passphrase.md @@ -16,8 +16,8 @@ Generates a cryptographically secure passphrase, based on a wordlist, or a password, and prints it to standard output. By default, it uses an embedded EFF Large Wordlist for passphrases. Passphrases with less than 6 words are considered insecure. A safe bet is between 6 and 7 words, -plus at least a number. For passwords, use at least 8 characters, but -prefer 12 or more. +plus at least a number. For passwords, use at least 12 characters, but +prefer 16 or more. Instead of words and numbers, a password (random string of printable characters from Python String standard) can be generated by **-p** | diff --git a/src/passphrase.py b/src/passphrase.py index ad6cb59..221aa68 100755 --- a/src/passphrase.py +++ b/src/passphrase.py @@ -7796,13 +7796,12 @@ 'zoom' ) -VERSION = '0.2.4' +VERSION = '0.2.5' MAX_NUM = 999999 MIN_NUM = 100000 WORDS_AMOUNT_MIN_DEFAULT = 6 # Just for EFF's Large Wordlist NUMS_AMOUNT_MIN_DEFAULT = 0 -PASSWD_LEN_MIN_DEFAULT = 8 # From NIST's recomendation: http://bit.ly/2bpkyBc ENTROPY_BITS_MIN = 77 # From EFF's post: http://bit.ly/2p96a2a @@ -7928,6 +7927,9 @@ def generate_password(length: int) -> str: import argparse from sys import exit + # entropy_bits(list(characters)) = 6.554588 + PASSWD_LEN_MIN_GOOD = ceil(ENTROPY_BITS_MIN / 6.554588) + parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description='Passphrase v{version} - Copyright HacKan ' @@ -7963,8 +7965,8 @@ def generate_password(length: int) -> str: maxnum=MAX_NUM, wordsamountmin=WORDS_AMOUNT_MIN_DEFAULT, numsamountmin=NUMS_AMOUNT_MIN_DEFAULT, - passwdmin=PASSWD_LEN_MIN_DEFAULT, - passwdpref=PASSWD_LEN_MIN_DEFAULT + 4, + passwdmin=PASSWD_LEN_MIN_GOOD, + passwdpref=PASSWD_LEN_MIN_GOOD + 4, version=VERSION ) ) @@ -7986,7 +7988,7 @@ def generate_password(length: int) -> str: "-p", "--password", type=bigger_than_zero, - const=PASSWD_LEN_MIN_DEFAULT, + const=PASSWD_LEN_MIN_GOOD, nargs='?', help="generate a password of specified lenght from all printable " "characters" @@ -8060,6 +8062,12 @@ def generate_password(length: int) -> str: exit() if passwordlen is not None: + if passwordlen < PASSWD_LEN_MIN_GOOD: + print_stderr( + "Warning: Insecure password length chosen! Should be bigger " + "than or equal to {}".format(PASSWD_LEN_MIN_GOOD) + ) + passphrase = generate_password(passwordlen) separator = '' else: @@ -8070,21 +8078,23 @@ def generate_password(length: int) -> str: # Then: entropy_w * amount_w + entropy_n * amount_n >= ENTROPY_BITS_MIN entropy_n = entropy_bits_nrange(MIN_NUM, MAX_NUM) entropy_w = entropy_bits(words) - amount_w_e = (ENTROPY_BITS_MIN - entropy_n * amount_n) / entropy_w + amount_w_good_e = (ENTROPY_BITS_MIN - entropy_n * amount_n) / entropy_w # print("entropy_n={}".format(entropy_n)) # print("entropy_w={}".format(entropy_w)) - # print("amount_w_e={}".format(amount_w_e)) + # print("amount_w_good_e={}".format(amount_w_good_e)) - if amount_w_e > -1: - amount_w_default = ceil(abs(amount_w_e)) + if amount_w_good_e > -1: + amount_w_good = ceil(abs(amount_w_good_e)) else: - amount_w_default = 0 + amount_w_good = 0 if amount_w is None: - amount_w = amount_w_default - elif amount_w < amount_w_default: - print_stderr("Warning: Insecure amount of words chosen! Should be " - "bigger than or equal to {}".format(amount_w_default)) + amount_w = amount_w_good + elif amount_w < amount_w_good: + print_stderr( + "Warning: Insecure amount of words chosen! Should be " + "bigger than or equal to {}".format(amount_w_good) + ) passphrase = generate(wordlist=words, amount_w=amount_w, amount_n=amount_n)