First version of pwgen
This commit is contained in:
		
							
								
								
									
										115
									
								
								bin/pwgen
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								bin/pwgen
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | |||||||
|  | #!/usr/bin/python3 | ||||||
|  | import argparse | ||||||
|  | import os | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | import pwgen | ||||||
|  |  | ||||||
|  | default_config_file=os.path.expanduser('~/.pwgen.cfg') | ||||||
|  |  | ||||||
|  | def main(): | ||||||
|  |  | ||||||
|  |     parser = argparse.ArgumentParser(description='Generate passwords') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--generate-config', '-g', | ||||||
|  |             action='store_true', | ||||||
|  |             help='Generate configuration file and then exit') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--config-file', '-c', | ||||||
|  |             help='Configuration file to use', | ||||||
|  |             default=default_config_file) | ||||||
|  |  | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--myspell-dir', '-i', | ||||||
|  |             help='Directory containing myspell dictionaries') | ||||||
|  |  | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--lang', '-l', | ||||||
|  |             help='Dictionary language to use') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--word-min-char', '-m', | ||||||
|  |             type=int, | ||||||
|  |             help='Minimum number of characters in a word') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--word-max-char', '-M', | ||||||
|  |             type=int, | ||||||
|  |             help='Maximum number of characters in a word') | ||||||
|  |  | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--words', '-w', | ||||||
|  |             type=int, | ||||||
|  |             help='Number of words to use in the passphrase') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--capitalize', '-C', | ||||||
|  |             choices=('true', 'false', 'random'), | ||||||
|  |             help='Capitalize the words') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--separators', '-s', | ||||||
|  |             help='Possible characters to use as separators') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--trailing-digits', '-d', | ||||||
|  |             type=int, | ||||||
|  |             help='Number of digits at the end of the passphrase') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--leading-digits', '-D', | ||||||
|  |             type=int, | ||||||
|  |             help='Number of digits at the start of the passphrase') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--special-chars', '-S', | ||||||
|  |             help='Possible characters to use as extra special characters') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--trailing-chars', '-p', | ||||||
|  |             type=int, | ||||||
|  |             help='Number of special characters to add at the end of the passphrase') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--leading-chars', '-P', | ||||||
|  |             type=int, | ||||||
|  |             help='Number of special characters to add at the start of the passphrase') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--passwords', '-n', | ||||||
|  |             type=int, | ||||||
|  |             help='Number of passwords to generate') | ||||||
|  |     parser.add_argument( | ||||||
|  |             '--max-length', '-L', | ||||||
|  |             type=int, | ||||||
|  |             help="Maximum length of the generated passwords. Full-knowledge "\ | ||||||
|  |                  "entropy calculation doesn't work when this is set.") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     args = vars(parser.parse_args()) | ||||||
|  |  | ||||||
|  |     config_file = args['config_file'] | ||||||
|  |     del args['config_file'] | ||||||
|  |  | ||||||
|  |     if not os.path.isfile(config_file): | ||||||
|  |         print("Missing configuration file; generating a new at {}".format(config_file)) | ||||||
|  |         conf = pwgen.update_config() | ||||||
|  |         pwgen.save_config(config_file, conf) | ||||||
|  |  | ||||||
|  |     conf = pwgen.get_config(config_file) | ||||||
|  |     save_config = args['generate_config'] | ||||||
|  |     del args['generate_config'] | ||||||
|  |  | ||||||
|  |     conf = pwgen.update_config(config=conf, **args) | ||||||
|  |  | ||||||
|  |     if save_config: | ||||||
|  |         print("Updating configuration file at {}".format(config_file)) | ||||||
|  |         pwgen.save_config(config_file, conf) | ||||||
|  |         sys.exit(0) | ||||||
|  |  | ||||||
|  |     pwds, seen_entropy = pwgen.generate_passwords(conf) | ||||||
|  |  | ||||||
|  |     print("Generated {} passwords".format(len(pwds))) | ||||||
|  |     if seen_entropy: | ||||||
|  |         print("Full-knowledge entropy is {0:.2g}".format(seen_entropy)) | ||||||
|  |     else: | ||||||
|  |         print("Unable to calculate full-knowledge entropy since max_length is used") | ||||||
|  |     print("Blind entropy\tPassword") | ||||||
|  |     print("========================") | ||||||
|  |     for pw in pwds.keys(): | ||||||
|  |         print("{:.5n}\t\t{}".format(pwds[pw]['entropy'], pw)) | ||||||
|  |     print("========================") | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     main() | ||||||
							
								
								
									
										194
									
								
								pwgen/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								pwgen/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,194 @@ | |||||||
|  | #!/usr/bin/python3 | ||||||
|  |  | ||||||
|  | import math | ||||||
|  | import os | ||||||
|  | import sys | ||||||
|  | #import secrets as random | ||||||
|  | import random | ||||||
|  |  | ||||||
|  | if sys.version_info[0] < 3: | ||||||
|  |     import ConfigParser as configparser | ||||||
|  | else: | ||||||
|  |     import configparser | ||||||
|  |  | ||||||
|  | if sys.version_info >= (3, 6): | ||||||
|  |     do_seed = False | ||||||
|  |     import secrets as random | ||||||
|  | else: | ||||||
|  |     do_seed = True | ||||||
|  |     import random | ||||||
|  |  | ||||||
|  | class PasswordLengthError(Exception): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  | def save_config(target_file, config): | ||||||
|  |     path = os.path.expanduser(target_file) | ||||||
|  |     with open(path, 'w') as f: | ||||||
|  |         config.write(f, False) | ||||||
|  |  | ||||||
|  | def update_config( | ||||||
|  |         config=None, | ||||||
|  |  | ||||||
|  |         myspell_dir='/usr/share/hunspell', | ||||||
|  |         lang='en_US', | ||||||
|  |         word_min_char=2, | ||||||
|  |         word_max_char=0, | ||||||
|  |  | ||||||
|  |         words=4, | ||||||
|  |         capitalize='random', | ||||||
|  |         separators=',.- _=:', | ||||||
|  |         trailing_digits=2, | ||||||
|  |         leading_digits=0, | ||||||
|  |         special_chars='!?.,:-_$/@', | ||||||
|  |         trailing_chars=1, | ||||||
|  |         leading_chars=0, | ||||||
|  |         passwords=5, | ||||||
|  |         max_length=0 | ||||||
|  |         ): | ||||||
|  |     if not config: | ||||||
|  |         conf = configparser.ConfigParser() | ||||||
|  |     else: | ||||||
|  |         conf = config | ||||||
|  |  | ||||||
|  |     def set_if_defined(conf, section, var, val): | ||||||
|  |         if val is not None: | ||||||
|  |             conf.set(section, var, str(val)) | ||||||
|  |  | ||||||
|  |     if not conf.has_section('dictionary'): | ||||||
|  |         conf.add_section('dictionary') | ||||||
|  |  | ||||||
|  |     set_if_defined(conf, 'dictionary', 'myspell_dir', myspell_dir) | ||||||
|  |     set_if_defined(conf, 'dictionary', 'lang', lang) | ||||||
|  |     set_if_defined(conf, 'dictionary', 'word_min_char', word_min_char) | ||||||
|  |     set_if_defined(conf, 'dictionary', 'word_max_char', word_max_char) | ||||||
|  |  | ||||||
|  |     if not conf.has_section('passwords'): | ||||||
|  |         conf.add_section('passwords') | ||||||
|  |     set_if_defined(conf, 'passwords', 'words', words) | ||||||
|  |     set_if_defined(conf, 'passwords', 'capitalize', capitalize) | ||||||
|  |     set_if_defined(conf, 'passwords', 'separators', separators) | ||||||
|  |     set_if_defined(conf, 'passwords', 'trailing_digits', trailing_digits) | ||||||
|  |     set_if_defined(conf, 'passwords', 'leading_digits', leading_digits) | ||||||
|  |     set_if_defined(conf, 'passwords', 'special_chars', special_chars) | ||||||
|  |     set_if_defined(conf, 'passwords', 'trailing_chars', trailing_chars) | ||||||
|  |     set_if_defined(conf, 'passwords', 'leading_chars', leading_chars) | ||||||
|  |     set_if_defined(conf, 'passwords', 'passwords', passwords) | ||||||
|  |     set_if_defined(conf, 'passwords', 'max_length', max_length) | ||||||
|  |     return conf | ||||||
|  |  | ||||||
|  |      | ||||||
|  | def get_config(f_name): | ||||||
|  |     conf = configparser.ConfigParser() | ||||||
|  |     conf.read(f_name) | ||||||
|  |     return conf | ||||||
|  |  | ||||||
|  | def _read_dictionary(f_name, word_min_chars, word_max_chars): | ||||||
|  |     words = set() | ||||||
|  |     chars = 0 | ||||||
|  |     with open(f_name, 'r') as f: | ||||||
|  |         for line in f: | ||||||
|  |             if not line: | ||||||
|  |                 continue | ||||||
|  |             first_char = line[0] | ||||||
|  |             if first_char in '1234567890,.-:': | ||||||
|  |                 continue | ||||||
|  |             word = line.split('/', 1)[0] | ||||||
|  |             word = word.strip() # remove newlines | ||||||
|  |             last_char = word[-1] | ||||||
|  |             if last_char in '-': | ||||||
|  |                 continue | ||||||
|  |             if word_min_chars and len(word) < word_min_chars: | ||||||
|  |                 continue | ||||||
|  |             if word_max_chars and len(word) > word_max_chars: | ||||||
|  |                 continue | ||||||
|  |             words.add(word) | ||||||
|  |             chars += len(word) | ||||||
|  |     return {'words': list(words), 'wordlength': int(chars/len(words))} | ||||||
|  |      | ||||||
|  |  | ||||||
|  | def generate_passwords(conf): | ||||||
|  |  | ||||||
|  |     nr_of_words = conf.getint('passwords', 'words') | ||||||
|  |     nr_of_ld = conf.getint('passwords', 'leading_digits') | ||||||
|  |     nr_of_td = conf.getint('passwords', 'trailing_digits') | ||||||
|  |     nr_of_lc = conf.getint('passwords', 'leading_chars') | ||||||
|  |     nr_of_tc = conf.getint('passwords', 'trailing_chars') | ||||||
|  |     special_chars = conf.get('passwords', 'special_chars') | ||||||
|  |     separators = conf.get('passwords', 'separators') | ||||||
|  |     capitalize = conf.get('passwords', 'capitalize') | ||||||
|  |     max_len = conf.getint('passwords', 'max_length') | ||||||
|  |  | ||||||
|  |     if do_seed: | ||||||
|  |         random.seed() | ||||||
|  |     res = {} | ||||||
|  |     dict_data = _read_dictionary( | ||||||
|  |             os.path.join(conf.get('dictionary', 'myspell_dir'), '{}.dic'.format(conf.get('dictionary', 'lang'))),  | ||||||
|  |             conf.getint('dictionary', 'word_min_char'), | ||||||
|  |             conf.getint('dictionary', 'word_max_char')) | ||||||
|  |  | ||||||
|  |     words = dict_data['words'] | ||||||
|  |     word_length = dict_data['wordlength'] | ||||||
|  |     approx_pwd_length = nr_of_words*word_length+nr_of_ld+nr_of_td+nr_of_lc+nr_of_tc+nr_of_words-1 | ||||||
|  |     if max_len and approx_pwd_length > max_len: | ||||||
|  |         raise PasswordLengthError( | ||||||
|  |                 "Password would be ~{} charactes long but max_len is {}; "\ | ||||||
|  |                 "please adjust max_lenght, words and/or the "\ | ||||||
|  |                 "trailing/leading settings.".format( | ||||||
|  |                     approx_pwd_length,  | ||||||
|  |                     max_len) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     capitalize_entropy = 1 | ||||||
|  |     if capitalize == 'random': | ||||||
|  |         capitalize_entropy = 2 | ||||||
|  |     # seen entropy is calculated from the password rules. | ||||||
|  |     # At the moment setting max_length breaks this calculation as not all | ||||||
|  |     # combinations of words are possible. | ||||||
|  |     if max_len: | ||||||
|  |         seen_entropy = False | ||||||
|  |     else: | ||||||
|  |         seen_entropy = math.log( | ||||||
|  |                 len(words)**nr_of_words *\ | ||||||
|  |                 len(separators) *\ | ||||||
|  |                 10**nr_of_ld *\ | ||||||
|  |                 10**nr_of_td *\ | ||||||
|  |                 len(special_chars)**nr_of_lc *\ | ||||||
|  |                 len(special_chars)**nr_of_tc *\ | ||||||
|  |                 capitalize_entropy | ||||||
|  |                 , 2) | ||||||
|  |  | ||||||
|  |     while len(res) < conf.getint('passwords', 'passwords'): | ||||||
|  |         separator = random.choice(separators) | ||||||
|  |         my_words = [] | ||||||
|  |          | ||||||
|  |         for i in range(nr_of_words): | ||||||
|  |             word = random.choice(words) | ||||||
|  |             if capitalize == 'random': | ||||||
|  |                 if random.choice((True, False)): | ||||||
|  |                     word = word.capitalize() | ||||||
|  |             elif capitalize == 'true': | ||||||
|  |                 word = word.capitalize() | ||||||
|  |             my_words.append(word) | ||||||
|  |  | ||||||
|  |         base_pwd = separator.join(my_words) | ||||||
|  |         leading_digits = ''.join(random.choice('1234567890') for i in range(nr_of_ld)) | ||||||
|  |         trailing_digits = ''.join(random.choice('1234567890') for i in range(nr_of_td)) | ||||||
|  |         leading_chars = ''.join(random.choice(special_chars) for i in range(nr_of_lc))  | ||||||
|  |         trailing_chars = ''.join(random.choice(special_chars) for i in range(nr_of_tc))  | ||||||
|  |         pwd = '{ld}{lc}{base}{tc}{td}'.format( | ||||||
|  |                 lc=leading_chars, | ||||||
|  |                 ld=leading_digits, | ||||||
|  |                 base=base_pwd, | ||||||
|  |                 td=trailing_digits, | ||||||
|  |                 tc=trailing_chars) | ||||||
|  |         # blind entropy is calculated only by the number of unique characters | ||||||
|  |         # and password length | ||||||
|  |         blind_entropy = math.log(len(''.join(set(pwd)))**len(pwd), 2) | ||||||
|  |  | ||||||
|  |         if max_len and len(pwd) > max_len: | ||||||
|  |             continue | ||||||
|  |         res[pwd] = {  | ||||||
|  |                 'length': len(pwd),  | ||||||
|  |                 'entropy': blind_entropy, | ||||||
|  |                 } | ||||||
|  |     return (res, seen_entropy) | ||||||
							
								
								
									
										34
									
								
								setup.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								setup.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | from os import environ | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from setuptools import setup | ||||||
|  | except ImportError: | ||||||
|  |     from distutils import setup | ||||||
|  |  | ||||||
|  | import pwgen | ||||||
|  |  | ||||||
|  | version = '0.1' | ||||||
|  |  | ||||||
|  | setup( | ||||||
|  |         name='pwgen', | ||||||
|  |         version=str(version), | ||||||
|  |         description="Passphrase generator", | ||||||
|  |         author="Fredrik Eriksson", | ||||||
|  |         author_email="pwgen@wb9.se", | ||||||
|  |         url="https://github.com/fredrik-eriksson/pwgen", | ||||||
|  |         platforms=['any'], | ||||||
|  |         license='BSD', | ||||||
|  |         packages=['pwgen'], | ||||||
|  |         classifiers=[ | ||||||
|  |             'Development Status :: 1 - Planning', | ||||||
|  |             'Environment :: Console', | ||||||
|  |             'Intended Audience :: System Administrators', | ||||||
|  |             'License :: OSI Approved :: BSD License', | ||||||
|  |             'Programming Language :: Python :: 2', | ||||||
|  |             'Programming Language :: Python :: 3', | ||||||
|  |             'Topic :: Utilities', | ||||||
|  |             ], | ||||||
|  |         keywords='password passphrase', | ||||||
|  |         scripts=['bin/pwgen'] | ||||||
|  |         ) | ||||||
		Reference in New Issue
	
	Block a user
	 Fredrik Eriksson
					Fredrik Eriksson