diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03baad0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,125 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 45a3cca..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: python -python: - - "2.7" - - "pypy" -install: python setup.py install -script: nosetests diff --git a/setup.py b/setup.py index e14aec4..3f4b00a 100644 --- a/setup.py +++ b/setup.py @@ -4,15 +4,16 @@ import sys, os version = '0.5' setup(name='twik', - version=version, - description="Twik is an application that makes it easier to generate secure and different passwords for each website.", - keywords='twik password hash', - author='Alexandre Possebom', - author_email='alexandrepossebom@gmail.com', - url='https://github.com/coxande/Twik', - license='GPLv3', - packages=['twik'], - entry_points = { - 'console_scripts': ['twik = twik.run:main'], - }, - ) + version=version, + description="Twik is an application that makes it easier to generate secure and different passwords for each website.", + keywords='twik password hash', + author='Alexandre Possebom', + author_email='alexandrepossebom@gmail.com', + url='https://github.com/coxande/Twik', + license='GPLv3', + packages=['twik'], + entry_points = { + 'console_scripts': ['twik = twik.run:main'], + }, +) + diff --git a/tests/test.py b/tests/test.py index 67681ab..37bdd75 100644 --- a/tests/test.py +++ b/tests/test.py @@ -2,73 +2,67 @@ import unittest from twik import twik class SimpleTest(unittest.TestCase): - def setUp(self): - self.t = twik.Twik() + def setUp(self): + self.t = twik.Twik() - def testPasswordAphanumericAndSpecialChars(self): - for chars in range(4, 27): - password = self.t.getpassword('tag', - 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', chars, 1) - if chars == 4: - self.assertEqual(password, 'm3/I') - if chars == 8: - self.assertEqual(password, 'mb/5AsJ9') - if chars == 12: - self.assertEqual(password, 'mb/5AsJ9Uon7') - if chars == 22: - self.assertEqual(password, 'mb15As*9Uon7ZzvcsXMjpV') - if chars == 26: - self.assertEqual(password, 'mb15AsJ9&on7ZzvcsXMjpVLTqQ') + def testPasswordAphanumericAndSpecialChars(self): + for chars in range(4, 27): + password = self.t.getpassword('tag', 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', chars, 1) + if chars == 4: + self.assertEqual(password, 'm3/I') + if chars == 8: + self.assertEqual(password, 'mb/5AsJ9') + if chars == 12: + self.assertEqual(password, 'mb/5AsJ9Uon7') + if chars == 22: + self.assertEqual(password, 'mb15As*9Uon7ZzvcsXMjpV') + if chars == 26: + self.assertEqual(password, 'mb15AsJ9&on7ZzvcsXMjpVLTqQ') - def testPasswordAlphanumeric(self): - for chars in range(4, 27): - password = self.t.getpassword('tag', - 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', chars, 2) - if chars == 4: - self.assertEqual(password, 'm31I') - if chars == 8: - self.assertEqual(password, 'mb15AsJ9') - if chars == 12: - self.assertEqual(password, 'mb15AsJ9Uon7') - if chars == 22: - self.assertEqual(password, 'mb15AsJ9Uon7ZzvcsXMjpV') - if chars == 26: - self.assertEqual(password, 'mb15AsJ9Uon7ZzvcsXMjpVLTqQ') + def testPasswordAlphanumeric(self): + for chars in range(4, 27): + password = self.t.getpassword('tag', 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', chars, 2) + if chars == 4: + self.assertEqual(password, 'm31I') + if chars == 8: + self.assertEqual(password, 'mb15AsJ9') + if chars == 12: + self.assertEqual(password, 'mb15AsJ9Uon7') + if chars == 22: + self.assertEqual(password, 'mb15AsJ9Uon7ZzvcsXMjpV') + if chars == 26: + self.assertEqual(password, 'mb15AsJ9Uon7ZzvcsXMjpVLTqQ') - def testPasswordNumeric(self): - for chars in range(4, 27): - password = self.t.getpassword('tag', - 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', chars, 3) - if chars == 4: - self.assertEqual(password, '4315') - if chars == 8: - self.assertEqual(password, '43154099') - if chars == 12: - self.assertEqual(password, '431540992657') - if chars == 22: - self.assertEqual(password, '4315409926570734032171') - if chars == 26: - self.assertEqual(password, '43154099265707340321711986') + def testPasswordNumeric(self): + for chars in range(4, 27): + password = self.t.getpassword('tag', 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', chars, 3) + if chars == 4: + self.assertEqual(password, '4315') + if chars == 8: + self.assertEqual(password, '43154099') + if chars == 12: + self.assertEqual(password, '431540992657') + if chars == 22: + self.assertEqual(password, '4315409926570734032171') + if chars == 26: + self.assertEqual(password, '43154099265707340321711986') - def testSize(self): - for chars in range(4, 27): - password = self.t.getpassword('tag', - 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', chars, 1) - self.assertEqual(len(password), chars) + def testSize(self): + for chars in range(4, 27): + password = self.t.getpassword('tag', 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', chars, 1) + self.assertEqual(len(password), chars) - def testSizeWrong(self): - password = self.t.getpassword('tag', - 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', 100, 1) - self.assertEqual(password, None) - def testSize(self): - password = self.t.getpassword('tag', - 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', 0, 1) - self.assertEqual(password, None) + def testSizeWrong(self): + password = self.t.getpassword('tag', 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', 100, 1) + self.assertEqual(password, None) + def testSize(self): + password = self.t.getpassword('tag', 'TFCY2AJI-NBPU-V01E-F7CP-PJIZNRKPF25W', 'foobar', 0, 1) + self.assertEqual(password, None) - def tearDown(self): - self.t = None + def tearDown(self): + self.t = None if __name__ == '__main__': - unittest.main() + unittest.main() diff --git a/twik/run.py b/twik/run.py index 24ac3fb..e5ac9f6 100644 --- a/twik/run.py +++ b/twik/run.py @@ -18,51 +18,53 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Twik. If not, see . """ -from util import Util -from twik import Twik +from .util import Util +from .twik import Twik import getpass import argparse import sys def main(): - """ - ALPHANUMERIC_AND_SPECIAL_CHARS=1 - ALPHANUMERIC=2 - NUMERIC=3 - """ - parser = argparse.ArgumentParser() - parser.add_argument("tag", type=str, - help="generate password for a specified tag") - parser.add_argument("-c", "--chars", type=int, - choices=range(4, 27), - metavar="[4-26]", - help="length of generated password [4-26]. Default: 12") - parser.add_argument("-p", "--profile", type=str, default=None, - help="profile to use. Default:'Personal'") - parser.add_argument("-t", "--passwordtype", type=int, choices=[1, 2, 3], - help=''' - 1 for ALPHANUMERIC_AND_SPECIAL_CHAR - 2 for ALPHANUMERIC - 3 for NUMERIC - Default: 1 - ''') - args = parser.parse_args() + """ + ALPHANUMERIC_AND_SPECIAL_CHARS=1 + ALPHANUMERIC=2 + NUMERIC=3 + """ + parser = argparse.ArgumentParser() + parser.add_argument("tag", type=str, + help="generate password for a specified tag") + parser.add_argument("-c", "--chars", type=int, + choices=list(range(4, 27)), + metavar="[4-26]", + help="length of generated password [4-26]. Default: 12") + parser.add_argument("-p", "--profile", type=str, default=None, + help="profile to use. Default:'Personal'") + parser.add_argument("-t", "--passwordtype", type=int, choices=[1, 2, 3], + help=''' + 1 for ALPHANUMERIC_AND_SPECIAL_CHAR + 2 for ALPHANUMERIC + 3 for NUMERIC + Default: 1 + ''') + args = parser.parse_args() - util = Util(args.tag, args.chars, args.passwordtype, args.profile) + util = Util(args.tag, args.chars, args.passwordtype, args.profile) - try: - master_key = getpass.getpass(prompt='Master Key: ') - except KeyboardInterrupt: - print "^C" - raise SystemExit(0) + try: + master_key = getpass.getpass(prompt='Master Key: ') + except KeyboardInterrupt: + print("^C") + raise SystemExit(0) - twik = Twik() - password = twik.getpassword(args.tag, util.get_privatekey(), master_key, - util.get_chars(), util.get_passord_type()) + twik = Twik() + password = twik.getpassword(args.tag, util.get_privatekey(), master_key, + util.get_chars(), util.get_passord_type()) - print "Your password is %s" % password + print(password, end='') + print('', file=sys.stderr) if __name__ == "__main__": - main() + main() + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/twik/twik.py b/twik/twik.py index c8bd2e9..ae565d2 100644 --- a/twik/twik.py +++ b/twik/twik.py @@ -24,79 +24,80 @@ You should have received a copy of the GNU General Public License along with Twik. If not, see . """ -from hashlib import sha1 -from util import Util -import hmac -import getpass import argparse +import base64 +import getpass +import hmac import sys +from hashlib import sha1 + +from .util import Util class Twik(object): - def injectcharacter(self, mhash, offset, reserved, seed, length, cstart, cnum): - pos0 = seed % length - pos = (pos0 + offset) % length - for i in range(0, length - reserved): - tmp = (pos0 + reserved + i) % length - char = ord(mhash[tmp]) - if char >= ord(cstart) and char < ord(cstart) + cnum: - return mhash - head = mhash[:pos] if pos > 0 else "" - inject = ((seed + ord(mhash[pos])) % cnum) + ord(cstart) - tail = mhash[pos+1:] if (pos + 1 < len(mhash)) else mhash - return head + chr(inject) + tail + def injectcharacter(self, mhash, offset, reserved, seed, length, cstart, cnum): + pos0 = seed % length + pos = (pos0 + offset) % length + for i in range(0, length - reserved): + tmp = (pos0 + reserved + i) % length + char = ord(mhash[tmp]) + if char >= ord(cstart) and char < ord(cstart) + cnum: + return mhash + head = mhash[:pos] if pos > 0 else '' + inject = ((seed + ord(mhash[pos])) % cnum) + ord(cstart) + tail = mhash[pos+1:] if (pos + 1 < len(mhash)) else mhash + return head + chr(inject) + tail - def removespecialcharacters(self, mhash, seed, length): - inputchars = list(mhash) - pivot = 0 - for i in range(0, length): - if not inputchars[i].isdigit() and not inputchars[i].isalpha(): - inputchars[i] = chr(((seed + pivot) % 26 + ord('A'))) - pivot = i + 1 - return "".join(inputchars) + def removespecialcharacters(self, mhash, seed, length): + inputchars = list(mhash) + pivot = 0 + for i in range(0, length): + if not inputchars[i].isdigit() and not inputchars[i].isalpha(): + inputchars[i] = chr(((seed + pivot) % 26 + ord('A'))) + pivot = i + 1 + return ''.join(inputchars) - def converttodigits(self, mhash, seed, length): - inputchars = list(mhash) - pivot = 0 - for i in range(0, length): - if not inputchars[i].isdigit(): - inputchars[i] = chr(((seed + ord(inputchars[pivot])) % 10 + - ord('0'))) - pivot = i + 1 - return "".join(inputchars) + def converttodigits(self, mhash, seed, length): + inputchars = list(mhash) + pivot = 0 + for i in range(0, length): + if not inputchars[i].isdigit(): + inputchars[i] = chr(((seed + ord(inputchars[pivot])) % 10 + + ord('0'))) + pivot = i + 1 + return ''.join(inputchars) - def generatehash(self, tag, key, length, password_type): - digest = hmac.new(key, tag, sha1).digest() - mhash = digest.encode('base64')[:-2] + def generatehash(self, tag, key, length, password_type): + digest = hmac.new(str.encode(key), str.encode(tag), sha1).digest() + mhash = base64.b64encode(digest)[:-1] + mhash = mhash.decode('utf-8') + seed = sum([ord(i) for i in mhash]) - seed = 0 - for i in range(0, len(mhash)): - seed += ord(mhash[i]) + # NUMERIC + if password_type == 3: + mhash = self.converttodigits(mhash, seed, length) + else: + mhash = self.injectcharacter(mhash, 0, 4, seed, length, '0', 10) + # ALPHANUMERIC_AND_SPECIAL_CHARS + if password_type == 1: + mhash = self.injectcharacter(mhash, 1, 4, seed, length, '!', 15) + mhash = self.injectcharacter(mhash, 2, 4, seed, length, 'A', 26) + mhash = self.injectcharacter(mhash, 3, 4, seed, length, 'a', 26) - """NUMERIC""" - if password_type == 3: - mhash = self.converttodigits(mhash, seed, length) - else: - mhash = self.injectcharacter(mhash, 0, 4, seed, length, '0', 10) - """ALPHANUMERIC_AND_SPECIAL_CHARS""" - if password_type == 1: - mhash = self.injectcharacter(mhash, 1, 4, seed, length, '!', 15) - mhash = self.injectcharacter(mhash, 2, 4, seed, length, 'A', 26) - mhash = self.injectcharacter(mhash, 3, 4, seed, length, 'a', 26) + # ALPHANUMERIC + if password_type == 2: + mhash = self.removespecialcharacters(mhash, seed, length) - """ALPHANUMERIC""" - if password_type == 2: - mhash = self.removespecialcharacters(mhash, seed, length) - - return mhash[:length] + return mhash[:length] - def getpassword(self, tag, private_key, master_key, length, password_type): - if length > 26 or length < 4: - return None - mhash = self.generatehash(private_key, tag, 24, 1) - password = self.generatehash(mhash, master_key, length, password_type) - return password + def getpassword(self, tag, private_key, master_key, length, password_type): + if length > 26 or length < 4: + return None + mhash = self.generatehash(private_key, tag, 24, 1) + password = self.generatehash(mhash, master_key, length, password_type) + return password + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/twik/util.py b/twik/util.py index 8f908b1..142e7dc 100644 --- a/twik/util.py +++ b/twik/util.py @@ -24,123 +24,125 @@ You should have received a copy of the GNU General Public License along with Twik. If not, see . """ +import configparser import os.path -import ConfigParser +import sys from random import SystemRandom def privatekeygenerator(): - """ - Generate new private key - """ - subgroups_length = [8, 4, 4, 4, 12] - subgroup_separator = '-' - allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - systemrandom = SystemRandom() - allowedcharslength = len(allowed_chars) - key = "" - for i in range(0, len(subgroups_length)): - for j in range(0, subgroups_length[i]): - key += allowed_chars[systemrandom.randrange(allowedcharslength)] - if i < (len(subgroups_length) -1): - key += subgroup_separator - return key + """ + Generate new private key + """ + subgroups_length = [8, 4, 4, 4, 12] + subgroup_separator = '-' + allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + systemrandom = SystemRandom() + allowedcharslength = len(allowed_chars) + key = "" + for i in range(0, len(subgroups_length)): + for j in range(0, subgroups_length[i]): + key += allowed_chars[systemrandom.randrange(allowedcharslength)] + if i < (len(subgroups_length) -1): + key += subgroup_separator + return key class Util(object): - """ - Class for deal with config file - """ - def __init__(self, tag, chars, pass_type, profile): - """ - Constructor - """ - homedir = os.path.expanduser('~') - self.filename = os.path.join(homedir, '.twik.conf') - self.config = ConfigParser.ConfigParser() - self.config.read(self.filename) - self.tag = tag - self.chars = chars - self.profile = profile - self.pass_type = pass_type - #Initialize default values - self.get_privatekey() + """ + Class for deal with config file + """ + def __init__(self, tag, chars, pass_type, profile): + """ + Constructor + """ + homedir = os.path.expanduser('~') + self.filename = os.path.join(homedir, '.twik.conf') + self.config = configparser.ConfigParser() + self.config.read(self.filename) + self.tag = tag + self.chars = chars + self.profile = profile + self.pass_type = pass_type + #Initialize default values + self.get_privatekey() - def writeconfig(self): - """ - Write config file - """ - with open(self.filename, 'w+') as fileconfig: - self.config.write(fileconfig) + def writeconfig(self): + """ + Write config file + """ + with open(self.filename, 'w+') as fileconfig: + self.config.write(fileconfig) - def get_privatekey(self): - """ - Get private key if not exists create new one - """ - private_key = '' - if self.profile == None and len(self.config.sections()) > 0: - for session in self.config.sections(): - if self.config.has_option(session, 'default') and self.config.getboolean(session, 'default') == True: - self.profile = session - break - if self.profile == None: - self.profile = self.config.sections()[0] - print 'Using profile : %s' % self.profile + def get_privatekey(self): + """ + Get private key if not exists create new one + """ + private_key = '' + if self.profile == None and len(self.config.sections()) > 0: + for session in self.config.sections(): + if self.config.has_option(session, 'default') and self.config.getboolean(session, 'default') == True: + self.profile = session + break + if self.profile == None: + self.profile = self.config.sections()[0] + print(f'Using profile: {self.profile}', file=sys.stderr) - if self.profile and self.config.has_option(self.profile, 'private_key'): - private_key = self.config.get(self.profile, 'private_key') - else: - private_key = privatekeygenerator() - if self.profile == None: - self.profile = 'Personal' - self.config.add_section(self.profile) - self.config.set(self.profile, 'private_key', private_key) - chars = self.chars - if chars == None: - chars = 12 - pass_type = self.pass_type - if pass_type == None: - pass_type = 1 - self.config.set(self.profile, 'chars', chars) - self.config.set(self.profile, 'password_type', pass_type) - if self.profile == 'Personal': - self.config.set(self.profile, 'default', 1) - self.writeconfig() - print 'New profile is generated' - self.config.read(self.filename) - return private_key + if self.profile and self.config.has_option(self.profile, 'private_key'): + private_key = self.config.get(self.profile, 'private_key') + else: + private_key = privatekeygenerator() + if self.profile == None: + self.profile = 'Personal' + self.config.add_section(self.profile) + self.config.set(self.profile, 'private_key', private_key) + chars = self.chars + if chars == None: + chars = 12 + pass_type = self.pass_type + if pass_type == None: + pass_type = 1 + self.config.set(self.profile, 'chars', chars) + self.config.set(self.profile, 'password_type', pass_type) + if self.profile == 'Personal': + self.config.set(self.profile, 'default', 1) + self.writeconfig() + print('New profile is generated') + self.config.read(self.filename) + return private_key - def get_chars(self): - config_key = '%s_chars' % self.tag + def get_chars(self): + config_key = f'{self.tag}_chars' - if self.config.has_option(self.profile, config_key) and self.chars == None: - self.chars = self.config.getint(self.profile, config_key) - else: - if self.chars == None and self.config.has_option(self.profile, 'chars'): - self.chars = self.config.getint(self.profile, 'chars') - self.config.set(self.profile, config_key, self.chars) - self.writeconfig() + if self.config.has_option(self.profile, config_key) and self.chars == None: + self.chars = self.config.getint(self.profile, config_key) + else: + if self.chars == None and self.config.has_option(self.profile, 'chars'): + self.chars = self.config.getint(self.profile, 'chars') + self.config.set(self.profile, config_key, str(self.chars)) + self.writeconfig() - if self.chars < 4 or self.chars > 26: - print 'invalid password length value from configuration using default' - self.chars = 12 + if self.chars < 4 or self.chars > 26: + print('invalid password length value from configuration using default') + self.chars = 12 - return self.chars + return self.chars - def get_passord_type(self): - config_key = '%s_password_type' % self.tag + def get_passord_type(self): + config_key = f'{self.tag}_password_type' - if self.config.has_option(self.profile, config_key) and self.pass_type == None: - self.pass_type = self.config.getint(self.profile, config_key) - else: - if self.pass_type == None and self.config.has_option(self.profile, 'password_type'): - self.pass_type = self.config.getint(self.profile, 'password_type') + if self.config.has_option(self.profile, config_key) and self.pass_type == None: + self.pass_type = self.config.getint(self.profile, config_key) + else: + if self.pass_type == None and self.config.has_option(self.profile, 'password_type'): + self.pass_type = self.config.getint(self.profile, 'password_type') - self.config.set(self.profile, config_key, self.pass_type) - self.writeconfig() + self.config.set(self.profile, config_key, str(self.pass_type)) + self.writeconfig() - if self.pass_type < 1 or self.pass_type > 3: - print 'invalid password type value from configuration using default' - self.pass_type = 1 + if self.pass_type < 1 or self.pass_type > 3: + print('invalid password type value from configuration using default') + self.pass_type = 1 - return self.pass_type + return self.pass_type + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4