From dcd61750418394f3317cc37d56eb7c6f719aff87 Mon Sep 17 00:00:00 2001 From: anqude <79022830+anqude@users.noreply.github.com> Date: Mon, 3 Apr 2023 22:38:52 +0400 Subject: [PATCH] Add files via upload --- PassmanCLI.py | 78 ++++++++++++++++++ PassmanGUI.py | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 18 +++++ logics.py | 77 ++++++++++++++++++ ui/icon.png | Bin 0 -> 14067 bytes 5 files changed, 386 insertions(+) create mode 100644 PassmanCLI.py create mode 100644 PassmanGUI.py create mode 100644 README.md create mode 100644 logics.py create mode 100644 ui/icon.png diff --git a/PassmanCLI.py b/PassmanCLI.py new file mode 100644 index 0000000..d81fe09 --- /dev/null +++ b/PassmanCLI.py @@ -0,0 +1,78 @@ +from logics import * + +def rewrite_to_file(lst): + try: + decrypt(fpassword) + with open('credentials', 'w') as f: + for i in lst: + f.write('site: '+i[0]+' login: '+i[1]+' password: '+i[2]+' date: '+i[3] + "\n") + encrypt(fpassword) + except: + pass + + + +def edit_info_file(index1): + lst=read_info_file(fpassword) + lst=list(map(list, lst)) + for i in range(len(lst)): + for j in range(len(lst[i])): + if lst[i][j]==index1: + index1=i + + if type(index1) is str: + print("Incorrect site name") + else: + log=input("New login:") + pas=input("New password:") + lst[index1][1]=log + lst[index1][2]=pas + rewrite_to_file(lst) + +def share_passw(index1): + lst=read_info_file(fpassword) + lst=list(map(list, lst)) + for i in range(len(lst)): + for j in range(len(lst[i])): + if lst[i][j]==index1: + index1=i + if type(index1) is str: + print("Incorrect site name") + else: + qr_share(lst[index1][0],lst[index1][1],lst[index1][2]) + +if __name__ == "__main__": + from stdiomask import getpass + fpassword = getpass('Введите пароль: ') + while True: + mode=int(input("select mode: 1-write, 2-read, 3-edit, 4-share \n")) + if mode==1: + website=input("Website: ") + login=input("Login: ") + password=input("Password: ") + write_to_file(website,login,password,fpassword) + if mode==2: + lst=read_info_file(fpassword) + print("==================================") + for i in lst: + for j in i: + print(j) + print("==================================") + if mode==3: + lst=read_info_file(fpassword) + print("==================================") + for i in lst: + for j in i: + print(j) + print("==================================") + index1=(input("Enter site name:")) + edit_info_file(index1) + if mode==4: + lst=read_info_file(fpassword) + print("==================================") + for i in lst: + for j in i: + print(j) + print("==================================") + index1=(input("Enter site name:")) + share_passw(index1) diff --git a/PassmanGUI.py b/PassmanGUI.py new file mode 100644 index 0000000..b1854f1 --- /dev/null +++ b/PassmanGUI.py @@ -0,0 +1,213 @@ +from customtkinter import CTkLabel,CTkButton,CTkEntry,CTkScrollableFrame,CTkTabview,CTk,get_appearance_mode +from logics import * +from tkinter import PhotoImage +import os +window = CTk() +window.title("PassMan by Anqude") +scriptdir=os.path.abspath(__file__) +os.chdir(scriptdir.removesuffix('/PassmanGUI.py')) +window.tk.call('wm', 'iconphoto', window._w, PhotoImage(file='./ui/icon.png')) +theme=get_appearance_mode() +if theme=="Dark": + bg_color="#242424" + fg_color="#dbdbdb" +else: + fg_color="#242424" + bg_color="#dbdbdb" +tabview = CTkTabview(window) +tab_write=tabview.add("Write") # add tab at the end +tab_read=tabview.add("Read") # add tab at the end +tabview.set("Write") # set currently visible tab + +def get_password(): + fpassword=Password_entry.get() + return fpassword +def enter(): + Pass_text.pack_forget() + Password_entry.pack_forget() + button.pack_forget() + get_password() + banan() + +Password_entry=CTkEntry(window) +Pass_text=CTkLabel(window,text="Password: ") +button=CTkButton(window,text="Log in!", command=enter) + +Pass_text.pack(fill="x", expand=True,padx=5,pady=5, side="top",anchor="n") +Password_entry.pack(fill="x", expand=True,padx=5,pady=5, side="top",anchor="n") +button.pack(fill="x", expand=True,padx=5,pady=5, side="top",anchor="n") + +Names=["Website:","Login:","Password:","Create time:"] + +def func(index): + inform=read_info_file(get_password()) + tabview.pack_forget() + Site_label=CTkLabel(window,text=Names[0]) + Site_val=CTkLabel(window) + Login_label=CTkLabel(window,text=Names[1]) + Login_val=CTkLabel(window) + Password_label=CTkLabel(window,text=Names[2]) + Password_val=CTkLabel(window) + def rewrite(): + Site_val.grid_forget() + Login_val.grid_forget() + Password_val.grid_forget() + CopyLoginB.grid_forget() + CopyPassB.grid_forget() + QRShare.grid_forget() + Site_entry=CTkEntry(window) + Site_entry.insert(0, inform[index][0]) + Login_entry=CTkEntry(window) + Login_entry.insert(0, inform[index][1]) + Password_entry=CTkEntry(window) + Password_entry.insert(0, inform[index][2]) + Site_entry.grid(row=0, column=1,padx=12,pady=10) + Login_entry.grid(row=1, column=1,padx=12,pady=10) + Password_entry.grid(row=2, column=1,padx=12,pady=10) + def save(): + edit_info_file(index,Site_entry.get(),Login_entry.get(),Password_entry.get(),get_password()) + readl() + DontEdit() + RewrireButton.configure(command=save,text="Save!") + ExitButton.grid_forget() + RewrireButton.grid_forget() + RewrireButton.grid(row=3, column=0,padx=12,pady=10) + + def DontEdit(): + Site_entry.grid_forget() + Login_entry.grid_forget() + Password_entry.grid_forget() + Site_label.grid_forget() + Login_label.grid_forget() + Password_label.grid_forget() + RewrireButton.grid_forget() + ExitButton.grid_forget() + func(index) + ExitButton.configure(command=DontEdit,text="Don`t save!") + ExitButton.grid_forget() + ExitButton.grid(row=3, column=1,padx=12,pady=10) + + + + def Exit(): + Site_label.grid_forget() + Site_val.grid_forget() + Login_label.grid_forget() + Login_val.grid_forget() + Password_label.grid_forget() + Password_val.grid_forget() + RewrireButton.grid_forget() + ExitButton.grid_forget() + ExitButton.grid_forget() + CopyLoginB.grid_forget() + CopyPassB.grid_forget() + QRShare.grid_forget() + tabview.pack(fill="both", expand=True,anchor='center') + + ExitButton=CTkButton(window,command=Exit,text="Exit!") + Site_val.configure(text=inform[index][0]) + Login_val.configure(text=inform[index][1]) + Password_val.configure(text=inform[index][2]) + Site_label.grid(row=0, column=0,padx=12,pady=10) + Site_val.grid(row=0, column=1,padx=12,pady=10) + Login_label.grid(row=1, column=0,padx=12,pady=10) + Login_val.grid(row=1, column=1,padx=12,pady=10) + Password_label.grid(row=2, column=0,padx=12,pady=10) + Password_val.grid(row=2, column=1,padx=12,pady=10) + RewrireButton=CTkButton(window,command=rewrite,text="🖊",width=10) + RewrireButton.grid(row=3, column=2,padx=12,pady=10) + ExitButton.grid(row=3, column=1,padx=12,pady=10) + def CopyLogin(): + from pyperclip import copy + copy(inform[index][1]) + CopyLoginB=CTkButton(window,command=CopyLogin,text="📋",width=10) + def CopyPass(): + from pyperclip import copy + copy(inform[index][2]) + + def genadiy(): + state=qr_image(inform[index][0],inform[index][1],inform[index][2],fg_color,bg_color) + from tkinter import Toplevel,Label + from PIL import ImageTk, Image + if state==True: + window = Toplevel() + window.geometry("200x200") + window.configure(bg=bg_color) + window.title("QR") + window.tk.call('wm', 'iconphoto', window._w, PhotoImage(file='qr.png')) + bg = ImageTk.PhotoImage(file="qr.png") + label = Label(window,background=bg_color,highlightbackground=bg_color) + label.pack(fill="both", expand=True,anchor='center') + counter_loop=[0] + def resize_image(win): + if counter_loop[0]%3==0: + image = Image.open("qr.png") + size=min(win.width,win.height) + resized = image.resize((size, size)) + image2 = ImageTk.PhotoImage(resized) + window.image2=image2 + label.configure(image=image2) + counter_loop.insert(0,counter_loop[0]+1) + window.bind("", resize_image) + window.mainloop() + else: + window = Toplevel() + window.configure(bg=bg_color) + window.title("Warning!") + window.tk.call('wm', 'iconphoto', window._w, PhotoImage(file='./ui/warn.png')) + label = Label(window,background=bg_color,highlightbackground=bg_color,text="Too much data to make QR!",foreground=fg_color,font=("Monospace",16)) + label.pack(fill="both", expand=True,anchor='center') + window.attributes('-topmost', True) + window.update() + window.mainloop() + CopyPassB=CTkButton(window,command=CopyPass,text="📋",width=10) + QRShare=CTkButton(window,command=genadiy,text="QR!") + CopyLoginB.grid(row=1, column=2,padx=12,pady=10) + CopyPassB.grid(row=2, column=2,padx=12,pady=10) + QRShare.grid(row=3, column=0,padx=12,pady=10) +scrollFrame = CTkScrollableFrame(master=tab_read) +scrollFrame.pack(fill="both",expand=True,padx=20, pady=20) +def readl(): + try: + buttonEdit.grid_forget() + Infos_label.grid_forget() + title.grid_forget() + except: + for i in range(2): + Title=CTkLabel(scrollFrame,text=Names[i]) + Title.grid(row=0, column=i,padx=12,pady=10) + inform=read_info_file(get_password()) + for i in range(len(inform)): + buttonEdit=CTkButton(scrollFrame,text="≡",command=lambda x=i: func(x),width=12) + buttonEdit.grid(row=i+1, column=2,padx=12,pady=10) + + for j in range(2): + Infos_label= CTkLabel(scrollFrame,text=inform[i][j]) + Infos_label.grid(row=i+1, column=j,padx=12,pady=10) + + +def banan(): + tabview.pack(fill="both", expand=True,anchor='center') + readl() + + +def write (): + write_to_file(Web_entry.get(),log_entry.get(),pas_entry.get(),get_password()) + readl() + + +Site_label=CTkLabel(tab_write,text=Names[0]) +Login_label=CTkLabel(tab_write,text=Names[1]) +Password_label=CTkLabel(tab_write,text=Names[2]) +Web_entry=CTkEntry(tab_write) +log_entry=CTkEntry(tab_write) +pas_entry=CTkEntry(tab_write) +SaveBut=CTkButton(tab_write,text="Save!", command=write) +Web_entry.grid(row=0, column=1,padx=12,pady=10) +log_entry.grid(row=1, column=1,padx=12,pady=10) +pas_entry.grid(row=2, column=1,padx=12,pady=10) +Site_label.grid(row=0, column=0,padx=12,pady=10) +Login_label.grid(row=1, column=0,padx=12,pady=10) +Password_label.grid(row=2, column=0,padx=12,pady=10) +SaveBut.grid(row=3, column=1,padx=12,pady=10) +window.mainloop() diff --git a/README.md b/README.md new file mode 100644 index 0000000..3287909 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Passman +## Yet simple password manager + +### Dependencies +**Python** +```sh +pip install tk pyperclip qrcode customtkinter pyAesCrypt +``` + +**On debian** +```sh +sudo apt install python3-tk python3-pil.imagetk xclip +``` + +**On fedora** +```sh +sudo dnf install python3-tkinter python3-pillow-tk xclip +``` diff --git a/logics.py b/logics.py new file mode 100644 index 0000000..1133dff --- /dev/null +++ b/logics.py @@ -0,0 +1,77 @@ +def qr_share(site,login, password): + import qrcode + qr = qrcode.QRCode() + qr.add_data('site = '+site+' login = '+login+' password = '+password) + return(qr.print_ascii()) + +def qr_image(site,login, password,fg_color,bg_color): + import qrcode + qr = qrcode.QRCode() + qr.add_data('site = '+site+' login = '+login+' password = '+password) + try: + qr.make(fit=True) + except: + return False + img = qr.make_image(fill_color=fg_color, back_color=bg_color) + img.save("qr.png") + return True +def encrypt(fpassword): + import pyAesCrypt + pyAesCrypt.encryptFile('credentials', 'credentials.aes', fpassword) + import os + os.remove("./credentials") +def decrypt(fpassword): + import pyAesCrypt + pyAesCrypt.decryptFile('credentials.aes', 'credentials', fpassword) + import os + os.remove("./credentials.aes") + + +def read_info_file(fpassword): + info_list = [] + try: + decrypt(fpassword) + with open('credentials', 'r') as f: + for string in f.readlines(): + site = string.split('site: ')[1].split(' ')[0] + login = string.split('login: ')[1].split(' ')[0] + password = string.split('password: ')[1].split(' ')[0] + cur_time = string.split('date: ')[1].strip() + info_list.append((site, login, password, cur_time)) + encrypt(fpassword) + except: + pass + return info_list + + +def write_to_file(site,login, password,fpassword): + try: + decrypt(fpassword) + except: + pass + from time import gmtime, strftime + cur_time=strftime("%Y-%m-%d %H:%M:%S", gmtime()).replace(" ","_") + with open('credentials', 'a') as f: + f.write('site: '+site+' login: '+login +' password: '+ password +' date: '+(cur_time) + "\n") + try: + encrypt(fpassword) + except: + pass + +def rewrite_to_file(lst,fpassword): + try: + decrypt(fpassword) + with open('credentials', 'w') as f: + for i in lst: + f.write('site: '+i[0]+' login: '+i[1]+' password: '+i[2]+' date: '+i[3] + "\n") + encrypt(fpassword) + except: + pass + +def edit_info_file(index,new_site,new_login,new_pas,fpassword): + lst=read_info_file(fpassword) + lst=list(map(list, lst)) + lst[index][0]=new_site + lst[index][1]=new_login + lst[index][2]=new_pas + rewrite_to_file(lst,fpassword) diff --git a/ui/icon.png b/ui/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b7fdbc7c8362b933053e37eff03485e2cb908df6 GIT binary patch literal 14067 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