From bc8da834e2d9ad3e16c5eea11646f36763581903 Mon Sep 17 00:00:00 2001 From: "A.A. Suvorov" Date: Mon, 28 Oct 2024 18:35:30 +0700 Subject: [PATCH] Smart Password Library v0.5.3 --- .gitignore | 160 ++++++++++++++++++++++++++++++ LICENSE | 28 ++++++ MANIFEST.in | 7 ++ README.md | 100 ++++++++++++++++++- requirements.txt | 0 requirements/requirements-dev.txt | 3 + setup.cfg | 39 ++++++++ setup.py | 13 +++ smartpasslib/__init__.py | 20 ++++ smartpasslib/generators.py | 148 +++++++++++++++++++++++++++ smartpasslib/managers.py | 145 +++++++++++++++++++++++++++ 11 files changed, 661 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 requirements.txt create mode 100644 requirements/requirements-dev.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 smartpasslib/__init__.py create mode 100644 smartpasslib/generators.py create mode 100644 smartpasslib/managers.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0b6f3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# 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/ +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 +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# 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 +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .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 + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# 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/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..699daaf --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2018-2024, A.A. Suvorov + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7e6a6de --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include LICENSE +include README.md +include requirements.txt +include tox.ini +include setup.cfg +include setup.py +global-exclude requirements/* \ No newline at end of file diff --git a/README.md b/README.md index fd25026..7371945 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,101 @@ -# smartpasslib +# Smart Passwords Library v0.5.3 *** -The project has been [changed and renamed](https://github.com/smartlegionlab/smartpassgen). +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/smartlegionlab/smartpasslib)](https://github.com/smartlegionlab/smartpasslib/) +[![PyPI - Downloads](https://img.shields.io/pypi/dm/smartpasslib?label=pypi%20downloads)](https://pypi.org/project/smartpasslib/) +![GitHub top language](https://img.shields.io/github/languages/top/smartlegionlab/smartpasslib) +[![PyPI](https://img.shields.io/pypi/v/smartpasslib)](https://pypi.org/project/smartpasslib) +[![GitHub](https://img.shields.io/github/license/smartlegionlab/smartpasslib)](https://github.com/smartlegionlab/smartpasslib/blob/master/LICENSE) +[![PyPI - Format](https://img.shields.io/pypi/format/smartpasslib)](https://pypi.org/project/smartpasslib) + +*** + +## Short Description: +___smartpasslib___ - Cross-platform library for generating smart passwords. + +This library allows you to generate smart passwords. +Smart passwords are passwords that are not stored anywhere, but are generated "on the fly". +Examples of applications created using this unique technology: + +- [Smart Password Generator (Console)](https://github.com/smartlegionlab/clipassgen/) +- [Smart Password Manager (Console)](https://github.com/smartlegionlab/clipassman/) +- [Smart Password Manager (Telegram Bot)](https://t.me/smartpasswordmanagerbot) +- [Smart Password Manager (Desktop)](https://github.com/smartlegionlab/smart_password_manager_desktop/) + +*** + +Author and developer: ___A.A. Suvorov.___ + +*** + +## Supported: + +- Linux: All. +- Windows: 7/8/10/11?. +- Termux (Android). + +*** + +## What is news: + +smartpasslib v0.5.3 + +- Moving the library to the old "historical" repository +- Minor improvements +- Minor fixes + +******* + +## Help: + +`pip install smartpasslib` + +```python +from smartpasslib import SmartPasswordMaster + + +login = 'login' +secret = 'secret' +length = 15 + +smart_password_master = SmartPasswordMaster() + +smart_password = smart_password_master.generate_smart_password(login=login, secret=secret, length=length) +smart_password2 = smart_password_master.generate_smart_password(login=login, secret=secret, length=length) + +check_passwords = smart_password == smart_password2 # True + +key = smart_password_master.generate_public_key(login, secret) + +check_data = smart_password_master.check_public_key(login, secret, key) # True +check_data2 = smart_password_master.check_public_key(login, 'secret2', key) # False + +``` + +### Information for developers: + +- `python setup.py sdist bdist_wheel` +- `twine upload dist/*` + +``` + +*** + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*** + + Licensed under the terms of the BSD 3-Clause License + (see LICENSE for details). + Copyright © 2018-2024, A.A. Suvorov + All rights reserved. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt new file mode 100644 index 0000000..ba996ed --- /dev/null +++ b/requirements/requirements-dev.txt @@ -0,0 +1,3 @@ +setuptools +wheel +twine \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..266c62d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,39 @@ +[metadata] +name = smartpasslib +version = attr: smartpasslib.__version__ +author = A.A. Suvorov +author_email = smartlegiondev@gmail.com +description = 'Cross-platform library for generating smart passwords.' +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/smartlegionlab +project_urls = + Documentation = https://github.com/smartlegionlab/smartpasslib/blob/master/README.md + Release notes = https://github.com/smartlegionlab/smartpasslib/releases +license = BSD 3-Clause License +classifiers = + Development Status :: 5 - Production/Stable + Environment :: Console + Intended Audience :: End Users/Desktop + Intended Audience :: System Administrators + License :: OSI Approved :: BSD License + Natural Language :: English + Operating System :: OS Independent + Operating System :: Microsoft :: Windows + Operating System :: POSIX :: Linux + Programming Language :: Python :: 3 :: Only + Topic :: Software Development :: Libraries :: Python Modules + Topic :: Utilities +keywords = + smart password + smart pass gen + smart password generator + smartpasslib + smartlegionlab + +[options] +python_requires = >=3.6 +packages = find: +include_package_data = true +zip_safe = false +install_requires = \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3dc318f --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +# -------------------------------------------------------- +# Licensed under the terms of the BSD 3-Clause License +# (see LICENSE for details). +# Copyright © 2018-2024, A.A Suvorov +# All rights reserved. +# -------------------------------------------------------- +# https://github.com/smartlegionlab/ +# -------------------------------------------------------- +from setuptools import setup, find_packages + +setup( + packages=find_packages(exclude=("requirements", )), +) \ No newline at end of file diff --git a/smartpasslib/__init__.py b/smartpasslib/__init__.py new file mode 100644 index 0000000..cd6df1f --- /dev/null +++ b/smartpasslib/__init__.py @@ -0,0 +1,20 @@ +# -------------------------------------------------------- +# Licensed under the terms of the BSD 3-Clause License +# (see LICENSE for details). +# Copyright © 2018-2024, A.A Suvorov +# All rights reserved. +# -------------------------------------------------------- +# https://github.com/smartlegionlab/ +# -------------------------------------------------------- +"""Smart Password Library - Cross-platform library for generating smart passwords.""" +from .generators import ( + SmartPasswordMaster, + HashGenerator, + UrandomGenerator, + SmartKeyGenerator, + BasePasswordGenerator, + StrongPasswordGenerator, + SmartPasswordGenerator, +) +from .managers import SmartPassword, SmartPasswordManager, SmartPasswordFactory +__version__ = '0.5.3' diff --git a/smartpasslib/generators.py b/smartpasslib/generators.py new file mode 100644 index 0000000..19c73f4 --- /dev/null +++ b/smartpasslib/generators.py @@ -0,0 +1,148 @@ +# -------------------------------------------------------- +# Licensed under the terms of the BSD 3-Clause License +# (see LICENSE for details). +# Copyright © 2018-2024, A.A Suvorov +# All rights reserved. +# -------------------------------------------------------- +# https://github.com/smartlegionlab/ +# -------------------------------------------------------- +"""Smart Random Generators.""" +import hashlib +import os +import random +import secrets +import string + + +class HashGenerator: + @classmethod + def generate(cls, text: str) -> str: + text = str(text) + sha = hashlib.sha3_512(text.encode('utf-8')) + return sha.hexdigest() + + +class UrandomGenerator: + @classmethod + def generate(cls, size=32): + return os.urandom(size) + + +class SmartKeyGenerator: + + @classmethod + def _create_key(cls, login='', secret='', public_step=15): + login_hash = cls.get_hash(text=login) + secret_hash = cls.get_hash(text=secret) + all_hash = cls.get_hash(text=login_hash + secret_hash) + for _ in range(public_step): + temp_hash = cls.get_hash(all_hash) + all_hash = cls.get_hash(all_hash + temp_hash + secret_hash) + return cls.get_hash(all_hash) + + @classmethod + def generate_public_key(cls, login='', secret=''): + return cls._create_key(login, secret, 60) + + @classmethod + def generate_private_key(cls, login='', secret=''): + return cls._create_key(login, secret, 30) + + @classmethod + def check_key(cls, login='', secret='', key=''): + return cls.generate_public_key(login=login, secret=secret) == key + + @classmethod + def get_hash(cls, text=''): + text = str(text) + return HashGenerator.generate(str(text.encode('utf-8'))) + + +class BasePasswordGenerator: + letters = string.ascii_letters + digits = string.digits + symbols = '!@#$%&^_' + + @classmethod + def generate(cls, length=10): + symbols_string = cls.letters + cls.digits + cls.symbols + return ''.join((random.choice(symbols_string) for _ in range(length))) + + +class StrongPasswordGenerator: + upper_letters = string.ascii_uppercase + lower_letters = string.ascii_lowercase + digits = string.digits + symbols = '!@#$%&^_' + + @classmethod + def generate(cls, length: int = 10) -> str: + if length < 4: + raise ValueError("The length cannot be less than 4.") + + result = [ + secrets.choice(cls.upper_letters), + secrets.choice(cls.lower_letters), + secrets.choice(cls.digits), + secrets.choice(cls.symbols), + ] + result += [ + secrets.choice(cls.upper_letters + cls.lower_letters + cls.digits + cls.symbols) + for _ in range(length - 4) + ] + secrets.SystemRandom().shuffle(result) + return ''.join(result) + + +class SmartPasswordGenerator: + @classmethod + def generate(cls, seed='', length=15, size=32) -> str: + if not seed: + seed = cls.get_seed(size) + cls._set_seed(seed) + password = BasePasswordGenerator.generate(length) + seed = str(cls.get_seed()) + cls._set_seed(seed) + return password + + @classmethod + def _set_seed(cls, seed): + seed = str(seed) + random.seed(seed) + return seed + + @classmethod + def get_seed(cls, size=32) -> bytes: + return UrandomGenerator.generate(size=size) + + +class SmartPasswordMaster: + + @staticmethod + def generate_base_password(length=10): + return BasePasswordGenerator.generate(length) + + @staticmethod + def generate_strong_password(length=10): + return StrongPasswordGenerator.generate(length) + + @classmethod + def generate_default_smart_password(cls, secret='', length=10): + return cls.generate_smart_password(secret=secret, length=length) + + @classmethod + def generate_smart_password(cls, login='', secret='', length=10): + seed = SmartKeyGenerator.generate_private_key(login, secret) + return SmartPasswordGenerator.generate(seed, length=length) + + @classmethod + def generate_public_key(cls, login='', secret=''): + return SmartKeyGenerator.generate_public_key(login, secret) + + @classmethod + def generate_private_key(cls, login='', secret=''): + return SmartKeyGenerator.generate_public_key(login, secret) + + @classmethod + def check_public_key(cls, login, secret, public_key): + return SmartKeyGenerator.check_key(login, secret, public_key) diff --git a/smartpasslib/managers.py b/smartpasslib/managers.py new file mode 100644 index 0000000..0d50b1b --- /dev/null +++ b/smartpasslib/managers.py @@ -0,0 +1,145 @@ +# -------------------------------------------------------- +# Licensed under the terms of the BSD 3-Clause License +# (see LICENSE for details). +# Copyright © 2024, A.A. Suvorov +# All rights reserved. +# -------------------------------------------------------- +# https://github.com/smartlegionlab/ +# -------------------------------------------------------- +import json +from pathlib import Path + +from smartpasslib import SmartPasswordMaster + + +class SmartPassword: + + def __init__(self, login='', key='', length=12): + self._login = login + self._length = length + self._key = key + + @property + def login(self): + return self._login + + @login.setter + def login(self, login): + self._login = login + + @property + def key(self): + return self._key + + @key.setter + def key(self, key): + self._key = key + + @property + def length(self): + return self._length + + @length.setter + def length(self, length): + self._length = length + + +class SmartPasswordFactory: + + @classmethod + def create_smart_password(cls, login, key, length): + return SmartPassword(login=login, key=key, length=length) + + +class SmartPasswordManager: + file_path = Path(Path.home()).joinpath('.cases.json') + smart_pass_factory = SmartPasswordFactory() + + def __init__(self): + self._passwords = {} + + @staticmethod + def generate_base_password(length=10): + return SmartPasswordMaster.generate_strong_password(length) + + @classmethod + def generate_default_smart_password(cls, secret='', length=10): + return SmartPasswordMaster.generate_default_smart_password(secret, length) + + @classmethod + def generate_smart_password(cls, login='', secret='', length=10): + return SmartPasswordMaster.generate_smart_password(login, secret, length) + + @classmethod + def check_public_key(cls, login, secret, public_key): + return SmartPasswordMaster.check_public_key(login, secret, public_key) + + @property + def passwords(self): + return self._passwords + + @property + def count(self): + return len(self._passwords) + + def add(self, password): + if password not in self._passwords: + self._passwords[password.login] = password + + def add_smart_password(self, login, secret, length): + key = SmartPasswordMaster.generate_public_key(login=login, secret=secret) + smart_password = self.smart_pass_factory.create_smart_password( + login=login, + key=key, + length=length + ) + self.add(smart_password) + return smart_password + + def add_passwords(self, passwords): + for password in passwords: + if isinstance(password, SmartPassword): + self.add(password) + + def get_password(self, login): + return self._passwords.get(login) + + def remove(self, login: str) -> None: + if login in self._passwords: + del self._passwords[login] + self.save_file() + + def load_file(self): + try: + with open(self.file_path, 'r') as f: + json_data = json.load(f) + except json.decoder.JSONDecodeError: + return {} + except FileNotFoundError: + self.file_path = Path(Path.home()).joinpath('.cases.json') + self.save_file() + return {} + else: + passwords = [ + SmartPassword( + login=json_data[obj]['login'], + key=json_data[obj]['key'], + length=max(10, min(json_data[obj]['length'], 1000)) + ) for obj in json_data] + self.add_passwords(passwords) + return passwords + + def save_file(self): + passwords_dict = {name: {'login': password.login, + 'length': password.length, + 'key': password.key} + for name, password in self._passwords.items()} + self._save_file(passwords_dict) + + def clear(self): + self._passwords = {} + + def _save_file(self, passwords: dict): + """Writes json data to a file.""" + with open(self.file_path, 'w') as f: + json.dump(passwords, f, indent=4)