diff --git a/README.md b/README.md index c88c9c8..34c5d9e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Passphrase -**Passphrase** is a tool to generate **cryptographically secure** passphrases and passwords. It's currently based on the security of Python's [Lib/secrets](https://docs.python.org/3/library/secrets.html#module-secrets), both passphrases and passwords are securelly generated using `secrets.choice()`: +**Passphrase** is a tool to generate **cryptographically secure** passphrases and passwords. + +For **Python 3.2+**, it's currently based on the security of [LibNaCl's](https://github.com/saltstack/libnacl) [randombytes_uniform](https://download.libsodium.org/doc/generating_random_data/#usage), both passphrases and passwords are securelly generated using `libnacl.randombytes_uniform()`. + +For **Python 3.6+**, it's currently based on the security of Python's [Lib/secrets](https://docs.python.org/3/library/secrets.html#module-secrets), both passphrases and passwords are securelly generated using `secrets.choice()` and `secrets.randbelow()`: > The `secrets` module is used for generating cryptographically strong random numbers suitable for managing data such as passwords, account authentication, security tokens, and related secrets. @@ -10,17 +14,25 @@ A secure passphrase must be of at least 5 words, but 7 is better, and maybe you ## Requirements -* Python 3.6 +For **Python 3.6+**: + +* flake8 [optional] for linting + +For **Python 3.2+**: + +* LibNaCl 1.5+ * flake8 [optional] for linting -[passphrase.py](/src/passphrase.py) is a stand-alone, self contained (the word list is embedded in it) script, and has no requirements besides Python 3.6 (because the `secrets` module is present since that Python version). I'm planning to implement PyNaCl so it can be used with Python 3.x. +[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`. ## How to use it -Just download the script, preferrably fom the [latest release](/releases/latest) - releases are always signed - and give it execution permission. It can be run as `:~$ python3.6 src/passphrase.py`, or if you copy it to /usr/local/bin (system-wide availability) or ~/.local/bin (user-wide availability), as `:~$ passphrase`. +Just download the script, preferrably fom the [latest release](https://github.com/HacKanCuBa/passphrase-py/releases/latest) - releases are always signed - and give it execution permission. It can be run as `:~$ python3.6 src/passphrase.py`, or if you copy it to /usr/local/bin (system-wide availability) or ~/.local/bin (user-wide availability), as `:~$ passphrase`. You can use `make install` to install it system-wide (requires root or `sudo`) or `make altinstall` for user-wide. Installing it simply copies the script to destination along with the man page. +To install requirements, use pip: `pip3 install -r requirements.txt`. + ### Examples of use Check the [man page](man/passphrase.md) for more information. @@ -74,7 +86,7 @@ gpg: encrypted with 1 passphrase 589ed823e9a84c56feb95ac58e7cf384626b9cbf4fda2a907bc36e103de1bad2 - ``` -### Generate a passphrase avoiding [shoulder surfing](https://en.wikipedia.org/wiki/Shoulder_surfing_(computer_security)) +#### Generate a passphrase avoiding [shoulder surfing](https://en.wikipedia.org/wiki/Shoulder_surfing_(computer_security)) ``` :~$ passphrase -q -o pass.txt @@ -98,4 +110,3 @@ gpg: encrypted with 1 passphrase You should have received a copy of the GNU General Public License along with this program. If not, see . - diff --git a/requirements.txt b/requirements.txt index e8cc222..02adb8a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ +libnacl~=1.5 flake8~=3.4 diff --git a/src/passphrase.py b/src/passphrase.py index 5a9e7ad..d8ebc52 100755 --- a/src/passphrase.py +++ b/src/passphrase.py @@ -3,10 +3,11 @@ """Generates passphrases based on a word list using cryptographically secure random number generator""" +from string import digits, ascii_letters, punctuation +from sys import stderr, version_info +from os.path import isfile -import os -import secrets -from sys import stderr +assert (version_info >= (3, 2)), "This script requires Python 3.2+" # EFF large wordlist WORDS_DEFAULT = ( @@ -7793,7 +7794,7 @@ NUMS_AMOUNT_MIN_DEFAULT = 0 PASSWD_LEN_MIN_DEFAULT = 8 -VERSION = '0.2.1' +VERSION = '0.2.3' def print_error(string: str) -> None: @@ -7815,23 +7816,6 @@ def read_words_from_diceware(inputfile: str) -> list: return words -def generate(wordlist: list, amount_w: int, amount_n: int) -> list: - passphrase = [] - for i in range(0, amount_w): - passphrase.append(secrets.choice(wordlist)) - - for i in range(0, amount_n): - passphrase.append(secrets.randbelow(MAX_NUM)) - - return passphrase - - -def generate_password(length: int) -> str: - import string - characters = string.digits + string.ascii_letters + string.punctuation - return ''.join(secrets.choice(characters) for i in range(0, length + 1)) - - def bigger_than_zero(value: int) -> int: ivalue = int(value) if ivalue < 0: @@ -7841,6 +7825,54 @@ def bigger_than_zero(value: int) -> int: return ivalue +if version_info >= (3, 6): + # Use Lib/secrets + from secrets import choice, randbelow + + def generate(wordlist: list, amount_w: int, amount_n: int) -> list: + passphrase = [] + for i in range(0, amount_w): + passphrase.append(choice(wordlist)) + + for i in range(0, amount_n): + passphrase.append(randbelow(MAX_NUM)) + + return passphrase + + def generate_password(length: int) -> str: + characters = digits + ascii_letters + punctuation + return ''.join(choice(characters) for i in range(0, length + 1)) + + +else: + # Use libnacl + from libnacl import randombytes_uniform + + def generate(wordlist: list, amount_w: int, amount_n: int) -> list: + passphrase = [] + index = None + num = None + for i in range(0, amount_w): + index = randombytes_uniform(len(wordlist)) + passphrase.append(wordlist[index]) + + for i in range(0, amount_n): + num = randombytes_uniform(MAX_NUM) + passphrase.append(num) + + return passphrase + + def generate_password(length: int) -> str: + characters = digits + ascii_letters + punctuation + passwd = [] + index = None + for i in range(0, length + 1): + index = randombytes_uniform(len(characters)) + passwd.append(characters[index]) + + return ''.join(passwd) + + if __name__ == "__main__": import argparse from sys import exit @@ -7966,7 +7998,7 @@ def bigger_than_zero(value: int) -> int: if inputfile is None: words = WORDS_DEFAULT - elif os.path.isfile(inputfile) is True: + elif isfile(inputfile) is True: if is_diceware is True: words = read_words_from_diceware(inputfile) else: