keymaster/main.py

553 lines
20 KiB
Python
Raw Normal View History

2021-08-27 11:02:59 +03:00
#!/usr/bin/env python3
# Key Master v1.05
# 03/03/2024
# https://t.me/ssleg © 2020 2024
2021-08-27 11:02:59 +03:00
import logging
2021-08-27 11:02:59 +03:00
import sqlite3
from datetime import datetime
2021-08-27 11:02:59 +03:00
from hashlib import md5, sha256
from os import path, getppid, kill
2021-08-27 11:02:59 +03:00
from random import randint
from signal import SIGKILL
2021-08-27 11:02:59 +03:00
import PySimpleGUI as Sg
import cryptocode
log_file = logging.FileHandler('key_master.log', 'w', 'utf-8')
log_file.setFormatter(logging.Formatter('%(levelname)s %(module)-13s [%(asctime)s] %(message)s'))
logging.basicConfig(level=logging.INFO, handlers=[log_file])
cc1 = 0
cc2 = 0
cc3 = 0
2021-08-27 11:02:59 +03:00
class LoginCard:
"""Класс хранения карточки логина/пароля"""
2021-08-27 11:02:59 +03:00
__slots__ = ['__row_id', '__encrypt_key', '__hash', '__name', '__login', '__password']
def __init__(self, encrypt_key, row_id=None, name=None, login=None, password=None):
global cc1, cc2, cc3
2021-08-27 11:02:59 +03:00
self.__row_id = row_id
self.__encrypt_key = encrypt_key
if row_id is None:
self.__name = name
self.__login = login
self.__password = password
control_string = self.__name + self.__login + self.__password
self.__hash = md5(control_string.encode()).hexdigest()
self.__write_card_to_db(mode=0)
else:
start = datetime.now()
2021-08-27 11:02:59 +03:00
cursor.execute('select * from logins where row_id=?', (row_id,))
row = cursor.fetchone()
db_name = row[1]
db_login = row[2]
db_password = row[3]
self.__hash = row[4]
end = datetime.now()
delta = end - start
if row_id != 1:
cc1 += delta.microseconds
cc3 += 1
start = datetime.now()
2021-08-27 11:02:59 +03:00
self.__name = cryptocode.decrypt(db_name, encrypt_key)
self.__login = cryptocode.decrypt(db_login, encrypt_key)
self.__password = cryptocode.decrypt(db_password, encrypt_key)
end = datetime.now()
delta = end - start
if row_id != 1:
cc2 += delta.microseconds
2021-08-27 11:02:59 +03:00
def is_valid(self):
control_string = self.__name + self.__login + self.__password
if isinstance(control_string, str):
2021-08-27 11:02:59 +03:00
control_hash = md5(control_string.encode()).hexdigest()
if control_hash == self.__hash:
return True
return False
def __write_card_to_db(self, mode):
db_name = cryptocode.encrypt(self.__name, self.__encrypt_key)
db_login = cryptocode.encrypt(self.__login, self.__encrypt_key)
db_password = cryptocode.encrypt(self.__password, self.__encrypt_key)
if mode == 0:
entry = (db_name, db_login, db_password, self.__hash)
cursor.execute('insert into logins (row_name, login, password, hash) values (?,?,?,?)', entry)
con.commit()
cursor.execute('select row_id from logins where row_name=?', (db_name,))
row = cursor.fetchone()
self.__row_id = row[0]
else:
entry = (db_name, db_login, db_password, self.__hash, self.__row_id)
cursor.execute('update logins set row_name=?, login=?, password=?, hash=? where row_id=?', entry)
con.commit()
def get_name(self):
return self.__name
def get_login(self):
return self.__login
def get_password(self):
return self.__password
def get_id(self):
return self.__row_id
def get_full_text(self):
full_text = 'Логин:\n' + self.__login + '\n\nПароль:\n' + self.__password
return full_text
def update_card(self, name, login, password):
self.__name = name
self.__login = login
self.__password = password
control_string = self.__name + self.__login + self.__password
self.__hash = md5(control_string.encode()).hexdigest()
self.__write_card_to_db(mode=1)
def delete_card(self):
cursor.execute('delete from logins where row_id=?', (self.__row_id,))
con.commit()
class AllCards:
"""Класс хранения всех карточек"""
2021-08-27 11:02:59 +03:00
__slots__ = ['__encrypt_key', '__cards']
def __init__(self, encrypt_key):
self.__encrypt_key = encrypt_key
row_ids = []
cursor.execute('select row_id from logins where row_id>1')
for row in cursor.fetchall():
row_ids.append(row[0])
self.__cards = []
for row in row_ids:
self.__cards.append(LoginCard(encrypt_key, row_id=row))
self.sort_cards()
def validate(self):
error_numbers = []
for card in self.__cards:
valid = card.is_valid()
if not valid:
error_numbers.append(card.get_id())
if len(error_numbers) > 0:
return False, error_numbers
else:
return True, error_numbers
def get_names_list(self):
names_list = []
for card in self.__cards:
names_list.append(card.get_name())
return names_list
def add_card(self, name, login, password):
self.__cards.append(LoginCard(self.__encrypt_key, name=name, login=login, password=password))
def get_card_by_name(self, name):
for card in self.__cards:
if card.get_name() == name:
return card.get_full_text()
def get_card_login(self, name):
for card in self.__cards:
if card.get_name() == name:
return card.get_login()
def get_card_password(self, name):
for card in self.__cards:
if card.get_name() == name:
return card.get_password()
def get_card_id(self, name):
for card in self.__cards:
if card.get_name() == name:
return card.get_id()
@staticmethod
def __sort_key(element: LoginCard):
return element.get_name()
def sort_cards(self):
self.__cards.sort(key=self.__sort_key)
def get_popular_logins(self):
logins = {}
logins_count = []
for card in self.__cards:
login = card.get_login()
index = logins.get(login)
if index is None:
logins[login] = len(logins_count)
logins_count.append([1, login])
else:
logins_count[index][0] += 1
popular = []
for item in logins_count:
if item[0] > 2:
2021-08-27 11:02:59 +03:00
popular.append(item[1])
return popular
def update_card(self, row_id, name, login, password):
for card in self.__cards:
if card.get_id() == row_id:
card.update_card(name, login, password)
break
def delete_card(self, name):
index = 0
while index < len(self.__cards):
card_name = self.__cards[index].get_name()
if card_name == name:
break
index += 1
self.__cards[index].delete_card()
self.__cards.pop(index)
# генератор паролей
def pass_generator(species_flag=False):
species = '!@#$%^&*()-_+=;:,./?\\|`~[]{}^' + "'" + '"<>~.'
new_pass = ''
species_count = 0
for i in range(0, 16):
cycle_flag = False
if species_flag:
if species_count < 4:
probability = randint(0, 5)
if probability == 4:
index = randint(0, 34)
new_pass += species[index]
species_count += 1
cycle_flag = True
if not cycle_flag:
char_index = randint(0, 35)
if char_index > 9:
char_index += 55
capital = randint(0, 1)
if capital == 0:
new_pass += chr(char_index)
else:
new_pass += chr(char_index + 32)
else:
new_pass += str(char_index)
return new_pass
2021-08-27 11:02:59 +03:00
# окно первого входа и задания пароля
def system_init():
status = False
password = ''
layout = [[Sg.Text(
'Задайте мастер-пароль шифрования.\nВнимание, пароль нигде не сохраняется и не может быть восстановлен!')],
[Sg.Text('Пароль:')],
[Sg.InputText(size=(20, 1), border_width=3)],
[Sg.Text('Повторите пароль:')],
[Sg.InputText(size=(20, 1), border_width=3)],
[Sg.Button('Ok'), Sg.Text('', key='Err_text', text_color='violetred4', font='Helvetica 15 bold')]]
2021-08-27 11:02:59 +03:00
window = Sg.Window("KeyMaster - приветствие", layout)
while True:
event, values = window.read()
if event == Sg.WIN_CLOSED:
break
if event == 'Ok':
input_word = values[0]
input_word2 = values[1]
if input_word == input_word2:
if len(input_word) >= 4:
status = True
password = sha256(input_word.encode()).hexdigest()
break
else:
err = 'Пароль слишком короткий, минимум 4 символа!'
window.Element('Err_text').Update(err)
else:
err = 'Пароли не совпадают!'
window.Element('Err_text').Update(err)
window.close()
return status, password
# окно ввода и проверки пароля
def system_login():
qt_enter_key1 = 'special 16777220'
qt_enter_key2 = 'special 16777221'
2021-08-27 11:02:59 +03:00
attempts = 3
status = False
password = ''
layout = [[Sg.Text('Пароль:'), Sg.InputText(size=(20, 1), border_width=3, password_char='*')],
[Sg.Button('Ok'), Sg.Text('', key='Err_text', text_color='violetred4', font='Helvetica 15 bold')]]
window = Sg.Window("KeyMaster - вход", layout, return_keyboard_events=True)
2021-08-27 11:02:59 +03:00
while True:
event, values = window.read()
if event == Sg.WIN_CLOSED:
break
if event == 'Ok' or event in ('\r', qt_enter_key1, qt_enter_key2, 'Return:36', 'KP_Enter:104'):
2021-08-27 11:02:59 +03:00
input_word = values[0]
if len(input_word) >= 4:
password = sha256(input_word.encode()).hexdigest()
tmp = LoginCard(encrypt_key=password, row_id=1)
if tmp.is_valid():
status = True
break
else:
attempts -= 1
if attempts > 0:
err = 'Неверный пароль,\nосталось попыток: ' + str(attempts)
window.Element('Err_text').Update(err)
else:
break
else:
attempts -= 1
if attempts > 0:
err = 'Пароль минимум 4 символа.\nосталось попыток: ' + str(attempts)
window.Element('Err_text').Update(err)
else:
break
window.close()
return status, password
# окно сообщения об ошибках
def err_msg(message):
# noinspection SpellCheckingInspection
layout = [[Sg.Text('Oшибка:')],
[Sg.Text(message)],
[Sg.Button('Ok')]]
window = Sg.Window("KeyMaster - ошибка", layout, modal=True)
while True:
event, values = window.read()
if event == 'Ok' or event == Sg.WIN_CLOSED:
break
window.close()
# окно подтверждения удаления
def confirm_delete(card_name):
confirm_flag = False
layout = [[Sg.Text('Вы уверены, что хотите удалить карточку:')],
[Sg.Text(card_name + '?')],
[Sg.Text('Это необратимое действие!!!', text_color='violetred4', font='Helvetica 15 bold')],
2021-08-27 11:02:59 +03:00
[Sg.Button('Ok'), Sg.Button('Отменить', focus=True)]]
window = Sg.Window("KeyMaster - подтверждение", layout, modal=True)
while True:
event, values = window.read()
if event == 'Отменить' or event == Sg.WIN_CLOSED:
break
if event == 'Ok':
confirm_flag = True
break
window.close()
return confirm_flag
# окно создания или редактирования карточки логина
def add_or_edit_card(used_list, popular_list, mode, name='', login='', password=''):
card_added = False
card_name = name
card_login = login
card_password = password
layout = [[Sg.Text('Имя:')],
[Sg.InputText(card_name, size=(42, 1), border_width=3)],
[Sg.Text('Логин:')],
[Sg.InputCombo(popular_list, default_value=card_login, size=(41, 1))],
[Sg.Text('Пароль:')],
[Sg.InputText(card_password, size=(42, 1), border_width=3, key='pass')],
[Sg.Checkbox('Использовать спецсимволы в пароле', key='special')],
2021-08-27 11:02:59 +03:00
[Sg.Button('Ok'), Sg.Button('Отменить'), Sg.Button('Сгенерировать пароль')]]
if mode == 0:
window = Sg.Window("KeyMaster - новая карточка", layout, modal=True)
else:
window = Sg.Window("KeyMaster - редактор карточки", layout, modal=True)
while True:
event, values = window.read()
if event == 'Отменить' or event == Sg.WIN_CLOSED:
break
if event == 'Ok':
error_flag = False
card_name = values[0]
card_login = values[1]
card_password = values['pass']
if card_name in used_list and mode == 0:
err_msg('такое имя уже используется.')
error_flag = True
else:
if card_name == '':
err_msg('введите имя.')
error_flag = True
if card_login == '' and not error_flag:
err_msg('введите логин.')
error_flag = True
if card_password == '' and not error_flag:
err_msg('введите пароль.')
error_flag = True
if not error_flag:
card_added = True
break
if event == 'Сгенерировать пароль':
new_pass = pass_generator(values['special'])
2021-08-27 11:02:59 +03:00
window.Element('pass').Update(new_pass)
window.close()
return card_added, card_name, card_login, card_password
# главное окно
def main():
global cc1, cc2, cc3
2021-08-27 11:02:59 +03:00
keys_disabled = True
my_cards = AllCards(master_key)
valid, error_numbers = my_cards.validate()
if not valid:
message = 'Внимание, карточки ## '
for number in error_numbers:
message += str(number) + ', '
message = message[0:len(message) - 2]
message += ' повреждены! Используйте бэкап.'
err_msg(message)
return
layout = [[Sg.Text('Главное окно')],
[Sg.Listbox(my_cards.get_names_list(), select_mode='LISTBOX_SELECT_MODE_SINGLE', enable_events=True,
size=(20, 10), key='list'), Sg.Text(key='card')],
[Sg.Button('Добавить'), Sg.Button('Редактировать', disabled=True, key='edit'),
Sg.Button('Скопировать пароль', disabled=True, key='copy')],
[Sg.Button('Удалить', disabled=True, key='delete'), Sg.Text(' '),
Sg.Button('Скопировать логин', disabled=True, key='copy_login')]]
2021-08-27 11:02:59 +03:00
window = Sg.Window("KeyMaster - главная", layout)
logging.info(
f'\nзагружено {cc3} карточек.\nвремя чтения {cc1} мкс.\nвремя расшифровки {round(cc2 / 1000000, 2)} секунд.\n'
'программа запущена.')
2021-08-27 11:02:59 +03:00
while True:
event, values = window.read()
if event == Sg.WIN_CLOSED:
break
name = window.Element('list').get()
if len(name) > 0:
if not keys_disabled:
window.Element('edit').update(disabled=True)
window.Element('copy').update(disabled=True)
window.Element('copy_login').update(disabled=True)
2021-08-27 11:02:59 +03:00
window.Element('delete').update(disabled=True)
keys_disabled = True
if event == 'Добавить':
added, name, login, password = add_or_edit_card(my_cards.get_names_list(), my_cards.get_popular_logins(),
mode=0)
if added:
my_cards.add_card(name=name, login=login, password=password)
my_cards.sort_cards()
window.Element('list').Update(my_cards.get_names_list())
if event == 'list':
name = window.Element('list').get()
if len(name) > 0:
if keys_disabled:
window.Element('edit').update(disabled=False)
window.Element('copy').update(disabled=False)
window.Element('copy_login').update(disabled=False)
2021-08-27 11:02:59 +03:00
window.Element('delete').update(disabled=False)
keys_disabled = False
card = my_cards.get_card_by_name(name[0])
window.Element('card').Update(card)
if event == 'copy':
name = window.Element('list').get()
if len(name) > 0:
password = my_cards.get_card_password(name[0])
Sg.clipboard_set(password)
if event == 'copy_login':
name = window.Element('list').get()
if len(name) > 0:
login = my_cards.get_card_login(name[0])
Sg.clipboard_set(login)
2021-08-27 11:02:59 +03:00
if event == 'edit':
name = window.Element('list').get()
if len(name) > 0:
card_name = name[0]
card_id = my_cards.get_card_id(card_name)
login = my_cards.get_card_login(card_name)
password = my_cards.get_card_password(card_name)
added, name_new, login_new, password_new = add_or_edit_card(my_cards.get_names_list(),
my_cards.get_popular_logins(), mode=1,
name=card_name, login=login,
password=password)
if added:
my_cards.update_card(row_id=card_id, name=name_new, login=login_new, password=password_new)
my_cards.sort_cards()
window.Element('list').Update(my_cards.get_names_list())
if event == 'delete':
name = window.Element('list').get()
if len(name) > 0:
card_name = name[0]
confirmed = confirm_delete(card_name)
if confirmed:
my_cards.delete_card(card_name)
my_cards.sort_cards()
window.Element('list').Update(my_cards.get_names_list())
window.close()
logging.info('программа завершена.')
2021-08-27 11:02:59 +03:00
# начало исполнения кода.
if __name__ == '__main__':
kill(getppid(), SIGKILL)
if not path.exists('base.sqlite'):
login_status, master_key = system_init()
if login_status:
con = sqlite3.connect('base.sqlite')
cursor = con.cursor()
cursor.executescript('''
create table logins
(
row_id integer
constraint logins_pk
primary key autoincrement,
row_name text not null,
login text not null,
password text not null,
hash text not null
);
''')
con.commit()
pass_save = LoginCard(master_key, name='master_login', login='0', password='0')
else:
2021-08-27 11:02:59 +03:00
con = sqlite3.connect('base.sqlite')
cursor = con.cursor()
logging.info('соединение с бд установлено, ожидаем пароль...')
login_status, master_key = system_login()
if login_status:
logging.info('логин - ок. запуск...')
main()