From 23e5912d1c42670f3e025afefd17db67b165f869 Mon Sep 17 00:00:00 2001 From: Evan Su <48808396+HACKERALERT@users.noreply.github.com> Date: Mon, 29 Mar 2021 22:43:41 -0400 Subject: [PATCH] Update Picocrypt.py --- src/Picocrypt.py | 1612 ++++++++++++++++++++++------------------------ 1 file changed, 781 insertions(+), 831 deletions(-) diff --git a/src/Picocrypt.py b/src/Picocrypt.py index 616d3cb..b9bdc5b 100644 --- a/src/Picocrypt.py +++ b/src/Picocrypt.py @@ -2,8 +2,7 @@ """ -Picocrypt v1.11 -Dependencies: argon2-cffi, pycryptodome, reedsolo, tkinterdnd2 +Picocrypt v1.12 Copyright (c) Evan Su (https://evansu.cc) Released under a GNU GPL v3 License https://github.com/HACKERALERT/Picocrypt @@ -12,113 +11,426 @@ https://github.com/HACKERALERT/Picocrypt """ -# Imports -from tkinter import filedialog,messagebox +# Import dependencies from threading import Thread from datetime import datetime -from argon2.low_level import hash_secret_raw,Type +from argon2.low_level import hash_secret_raw +from argon2.low_level import Type as argonType from Crypto.Cipher import ChaCha20_Poly1305 -from Crypto.Hash import SHA3_512 as sha3_512 -from secrets import compare_digest +from Crypto.Hash import SHA3_512,BLAKE2b +from hmac import compare_digest +from creedsolo import RSCodec,ReedSolomonError from os import urandom,fsync,remove,system -from os.path import getsize,expanduser,isdir -from os.path import dirname,abspath,realpath -from os.path import join as pathJoin -from os.path import split as pathSplit -from tkinterdnd2 import TkinterDnD,DND_FILES -from zipfile import ZipFile +from os.path import getsize,expanduser,isdir,exists,dirname,abspath,realpath +from os.path import join as pathJoin,split as pathSplit from pathlib import Path -from shutil import rmtree +from zipfile import ZipFile +from tkinterdnd2 import TkinterDnD,DND_FILES +from ttkthemes import ThemedStyle +from time import sleep +import re import sys import tkinter import tkinter.ttk import tkinter.scrolledtext import webbrowser import platform -from creedsolo import RSCodec,ReedSolomonError -# Tk/Tcl is a little barbaric, so I'm disabling -# high DPI so it doesn't scale bad and look horrible -try: - from ctypes import windll - windll.shcore.SetProcessDpiAwareness(0) -except: - pass - -# Global variables and strings +# Global variables rootDir = dirname(realpath(__file__)) -inputFile = "" -outputFile = "" -outputPath = "" -password = "" -ad = "" -kept = False working = False -gMode = None -headerRsc = False +mode = False +inputFile = False +outputFile = False +rs128 = False +rs13 = False allFiles = False -draggedFolderPaths = False -files = False -adString = "File metadata (used to store some text along with the file):" -compressingNotice = "Compressing files together..." -passwordNotice = "Error. The provided password is incorrect." -corruptedNotice = "Error. The input file is corrupted." -veryCorruptedNotice = "Error. The input file and header keys are badly corrupted." -modifiedNotice = "Error. The input file has been intentionally modified." -kCorruptedNotice = "The input file is corrupted, but the output has been kept." -kModifiedNotice = "The input file has been intentionally modified, but the output has been kept." -kVeryCorruptedNotice = "The input file is badly corrupted, but the output has been kept." -derivingNotice = "Deriving key (takes a few seconds)..." -keepNotice = "Keep decrypted output even if it's corrupted or modified" -eraseNotice = "Securely erase and delete original file" -erasingNotice = "Securely erasing original file(s)..." -overwriteNotice = "Output file already exists. Would you like to overwrite it?" -cancelNotice = "Exiting now will lead to broken output. Are you sure?" -rsNotice = "Prevent corruption using Reed-Solomon" -rscNotice = "Creating Reed-Solomon tables..." -unknownErrorNotice = "Unknown error occured. Please try again." +onlyFolders = False +onlyFiles = False +startTime = False +previousTime = False +done = False +stopUpdating = False +reedsolo = False +reedsoloFixed = False +reedsoloErrors = False -# Create root Tk +# Strings +strings = [ + "File metadata (used to store some text along with the file):", + "Compressing files together...", + "Error. The provided password is incorrect.", + "Error. The input file is corrupted.", + "Error. The input file and header keys are badly corrupted.", + "Error. The input file has been intentionally modified.", + "The input file is corrupted, but the output has been kept.", + "The input file has been intentionally modified, but the output has been kept.", + "The input file is badly corrupted, but the output has been kept.", + "Deriving key (takes a few seconds)...", + "Keep decrypted output even if it's corrupted or modified", + "Securely erase and delete original file", + "Securely erasing original file(s)...", + "Output file already exists. Would you like to overwrite it?", + "Exiting now will lead to broken output. Are you sure?", + "Prevent corruption using Reed-Solomon", + "Error. Folder(s) and/or file(s) are empty.", + "Unknown error occured. Please try again.", + "Drag and drop file(s) and folder(s) into this window.", + "File metadata (read-only):", + "Error. The input file couldn't be decoded as UTF-8." +] + +# Create root tk tk = TkinterDnD.Tk() -tk.geometry("480x470") +tk.geometry("480x512") tk.title("Picocrypt") -if platform.system()=="Darwin": - tk.configure(background="#edeced") -else: - tk.configure(background="#ffffff") tk.resizable(0,0) +tk.configure(background="#f5f6f7") +ThemedStyle(tk).set_theme("arc") -# Try setting window icon if included with Picocrypt +# Enable high DPI on Windows +def Get_HWND_DPI(window_handle): + from ctypes import windll,pointer,wintypes + windll.shcore.SetProcessDpiAwareness(1) + DPI100pc = 96 + DPI_type = 0 + winH = wintypes.HWND(window_handle) + monitorhandle = windll.user32.MonitorFromWindow( + winH,wintypes.DWORD(2) + ) + X = wintypes.UINT() + Y = wintypes.UINT() + try: + windll.shcore.GetDpiForMonitor( + monitorhandle,DPI_type,pointer(X),pointer(Y) + ) + return X.value*2,Y.value*2,(X.value+Y.value)/(2*DPI100pc) + except Exception: + return 96,96,1 +def TkGeometryScale(s,cvtfunc): + patt = r"(?P\d+)x(?P\d+)\+(?P\d+)\+(?P\d+)" + R = re.compile(patt).search(s) + G = str(cvtfunc(R.group("W")))+"x" + G += str(cvtfunc(R.group("H")))+"+" + G += str(cvtfunc(R.group("X")))+"+" + G += str(cvtfunc(R.group("Y"))) + return G +def MakeTkDPIAware(TKGUI): + TKGUI.DPI_X,TKGUI.DPI_Y,TKGUI.DPI_scaling = Get_HWND_DPI(TKGUI.winfo_id()) + TKGUI.TkScale = lambda v:int(float(v)*TKGUI.DPI_scaling) + TKGUI.TkGeometryScale = lambda s:TkGeometryScale(s,TKGUI.TkScale) +if platform.system()=="Windows": + MakeTkDPIAware(tk) + +# Add some styling +style = tkinter.ttk.Style() + +# Try setting window icon if it exists try: favicon = tkinter.PhotoImage(file="./key.png") tk.iconphoto(False,favicon) except: pass -# Some styling -s = tkinter.ttk.Style() -s.configure("TCheckbutton",background="#ffffff") +# Dummy button used for removing ugly highlights +dummy = tkinter.ttk.Button(tk) +dummy.place(x=480,y=0) -# Event when user drags file(s) and folder(s) into window -def inputSelected(draggedFile): - global inputFile,working,headerRsc,allFiles,draggedFolderPaths,files +# Label that shows the input file(s) +inputString = tkinter.StringVar(tk) +inputString.set(strings[18]) +inputLabel = tkinter.ttk.Label( + tk, + textvariable=inputString +) +inputLabel.place(x=20,y=18) + +# Clear input file(s) +clearInput = tkinter.ttk.Button( + tk, + text="Clear", + command = lambda:resetUI() +) +clearInput.place(x=400,y=12,width=60,height=28) +clearInput["state"] = "disabled" + +# Separator for aesthetics +separator = tkinter.Frame( + tk, + bg="#dfe3ed", + height=1 +) +separator.place(x=20,y=39,width=438) + +# Label to ask user what to save output as +outputString = tkinter.StringVar(tk) +outputString.set("Save output as:") +outputLabel = tkinter.ttk.Label( + tk, + textvariable=outputString +) +outputLabel.place(x=20,y=48) + +outputLabel["state"] = "disabled" + +# A ".pcv" extension shown next to output box +pcvString = tkinter.StringVar(tk) +pcvString.set(".pcv") +pcvLabel = tkinter.ttk.Label( + tk, + textvariable=pcvString +) +pcvLabel.place(x=434,y=68) + +# A frame to allow output box to fill width +outputFrame = tkinter.Frame( + tk, + width=440, + height=24 +) +outputFrame.place(x=20,y=66) +outputFrame.columnconfigure(0,weight=10) +outputFrame.grid_propagate(False) + +# Output box to allow user to change output name and path +outputInput = tkinter.ttk.Entry(outputFrame) +outputInput.grid(sticky="nesw") +outputInput["state"] = "disabled" + +# Prompt user to enter password +passwordString = tkinter.StringVar(tk) +passwordString.set("Password:") +passwordLabel = tkinter.ttk.Label( + tk, + textvariable=passwordString +) +passwordLabel.place(x=20,y=100) +passwordLabel["state"] = "disabled" + +# Allow password input to fill width +passwordFrame = tkinter.Frame( + tk, + width=440, + height=24 +) +passwordFrame.place(x=20,y=118) +passwordFrame.columnconfigure(0,weight=10) +passwordFrame.grid_propagate(False) + +# Password input box +passwordInput = tkinter.ttk.Entry( + passwordFrame, + show="\u2022" +) +passwordInput.grid(sticky="nesw") +passwordInput["state"] = "disabled" + +# Prompt user to confirm password +cPasswordString = tkinter.StringVar(tk) +cPasswordString.set("Confirm password:") +cPasswordLabel = tkinter.ttk.Label( + tk, + textvariable=cPasswordString +) +cPasswordLabel.place(x=20,y=150) +cPasswordLabel["state"] = "disabled" + +# Allow confirm password input to fill width +cPasswordFrame = tkinter.Frame( + tk, + width=440, + height=24 +) +cPasswordFrame.place(x=20,y=168) +cPasswordFrame.columnconfigure(0,weight=10) +cPasswordFrame.grid_propagate(False) + +# Confirm password input box +cPasswordInput = tkinter.ttk.Entry( + cPasswordFrame, + show="\u2022" +) +cPasswordInput.grid(sticky="nesw") +cPasswordInput["state"] = "disabled" + +# Prompt user for optional metadata +metadataString = tkinter.StringVar(tk) +metadataString.set(strings[0]) +metadataLabel = tkinter.ttk.Label( + tk, + textvariable=metadataString +) +metadataLabel.place(x=20,y=202) +metadataLabel["state"] = "disabled" + +# Frame so metadata box can fill width +metadataFrame = tkinter.Frame( + tk, + width=439, + height=99 +) +metadataFrame.place(x=20,y=220) +metadataFrame.columnconfigure(0,weight=10) +metadataFrame.rowconfigure(0,weight=10) +metadataFrame.grid_propagate(False) +metadataFrame.config(bg="#e5eaf0") + +# Metadata text box +metadataInput = tkinter.scrolledtext.ScrolledText( + metadataFrame, + exportselection=0, + height=5, + padx=5, + pady=5 +) +metadataInput.config(font=("Consolas",12)) +metadataInput.grid(row=0,column=0,sticky="nesw",padx=1,pady=1) +metadataInput.config(borderwidth=0) +metadataInput.config(bg="#fbfcfc") +metadataInput["state"] = "disabled" +metadataInput.bind( + "", + lambda e:metadataBoxUI("in") +) +metadataInput.bind( + "", + lambda e:metadataBoxUI("out") +) +# Tkinter's Text() boxes are ugly, so I beautify it manually +def metadataBoxUI(what): + if what=="in": + if metadataInput.cget("bg")=="#ffffff": + metadataFrame.config(bg="#78a7e5") + else: + metadataFrame.config(bg="#d8ddea") + +# Check box for keeping corrupted or modified output +keep = tkinter.IntVar(tk) +keepBtn = tkinter.ttk.Checkbutton( + tk, + text=strings[10], + variable=keep, + onvalue=1, + offvalue=0, + command=lambda:dummy.focus() +) +keepBtn.place(x=18,y=329) +keepBtn["state"] = "disabled" + +# Check box for securely erasing original files +erase = tkinter.IntVar(tk) +eraseBtn = tkinter.ttk.Checkbutton( + tk, + text=strings[11], + variable=erase, + onvalue=1, + offvalue=0, + command=lambda:dummy.focus() +) +eraseBtn.place(x=18,y=349) +eraseBtn["state"] = "disabled" + +# Check box for enabling Reed-Solomon anti-corruption +rs = tkinter.IntVar(tk) +rsBtn = tkinter.ttk.Checkbutton( + tk, + text=strings[15], + variable=rs, + onvalue=1, + offvalue=0, + command=lambda:dummy.focus() +) +rsBtn.place(x=18,y=369) +rsBtn["state"] = "disabled" + +# Frame so start and cancel button can fill width +startFrame = tkinter.Frame( + tk, + width=440, + height=29 +) +startFrame.place(x=20,y=402) +startFrame.columnconfigure(0,weight=10) +startFrame.grid_propagate(False) +startFrame.config(background="#ffffff") + +# Start button +startBtn = tkinter.ttk.Button( + startFrame, + text="Start", + command=lambda:Thread(target=work,daemon=True).start() +) +startBtn.grid(row=0,column=0,stick="nesw") +startBtn["state"] = "disabled" + +# Cancel button +cancelBtn = tkinter.ttk.Button( + startFrame, + text="Cancel" +) +cancelBtn.grid(stick="nesw") +cancelBtn.grid(row=0,column=1) +cancelBtn["state"] = "disabled" + +# Progress bar +progress = tkinter.ttk.Progressbar( + tk, + orient=tkinter.HORIZONTAL, + length=440, + mode="determinate" +) +progress.place(x=20,y=439) + +# Status label +statusString = tkinter.StringVar(tk) +statusString.set("Ready.") +status = tkinter.ttk.Label( + tk, + textvariable=statusString +) +status.place(x=20,y=453) + +# Credits +hint = "Created by Evan Su. Click for details and source." +creditsString = tkinter.StringVar(tk) +creditsString.set(hint) +credits = tkinter.ttk.Label( + tk, + textvariable=creditsString, + cursor="hand2" +) +credits.place(x=20,y=480) +source = "https://github.com/HACKERALERT/Picocrypt" +credits.bind("",lambda e:webbrowser.open(source)) +credits["state"] = "disabled" + +# Version +versionString = tkinter.StringVar(tk) +versionString.set("v1.12") +version = tkinter.ttk.Label( + tk, + textvariable=versionString +) +version["state"] = "disabled" +version.place(x=430,y=480) + +# Files have been dragged +def filesDragged(draggedFiles): + global inputFile,rs128,onlyFiles,mode,onlyFolders,allFiles resetUI() - dummy.focus() status.config(cursor="") status.bind("",lambda e:None) - - # Use try to handle errors + # Use try to catch file errors try: - # Create list of input files + # Create lists to track files dragged + onlyFiles = [] + onlyFolders = [] allFiles = [] - files = [] - draggedFolderPaths = [] - suffix = "" - tmp = [i for i in draggedFile] + tmpName = "" + tmp = [i for i in draggedFiles] res = [] within = False - tmpName = "" - + """ The next for loop parses data return by tkinterdnd2's file drop method. When files and folders are dragged, the output (the 'draggedFile' parameter) @@ -141,659 +453,436 @@ def inputSelected(draggedFile): tmpName = "" else: if i==" " and not within: - if tmpName!="": + if tmpName: res.append(tmpName) tmpName = "" else: tmpName += i if tmpName: res.append(tmpName) - - allFiles = [] - files = [] - - # Check each thing dragged by user + + # Check each item dragged by user for i in res: - # If there is a directory, recursively add all files to 'allFiles' + # If it's a directory, recursively add all files if isdir(i): - # Record the directory for secure wipe (if necessary) - draggedFolderPaths.append(i) + onlyFolders.append(i) tmp = Path(i).rglob("*") for p in tmp: allFiles.append(abspath(p)) - # Just a file, add it to files + # Just a file, add it to 'onlyFiles' else: - files.append(i) - - # If there's only one file, set it as input file - if len(files)==1 and len(allFiles)==0: - inputFile = files[0] - files = [] + onlyFiles.append(i) + + # If there's only one file, set it as 'inputFile' + if len(onlyFiles)==1 and not len(allFiles): + inputFile = onlyFiles[0] + onlyFiles = [] else: inputFile = "" - + # Decide if encrypting or decrypting if inputFile.endswith(".pcv"): + mode = "decrypt" suffix = " (will decrypt)" + + # Read file metadata fin = open(inputFile,"rb") - - # Read file metadata (a little complex) - tmp = fin.read(139) - reedsolo = False - if tmp[0]==43: - reedsolo = True - tmp = tmp[1:] - else: - tmp = tmp[:-1] - tmp = bytes(headerRsc.decode(tmp)[0]) - tmp = tmp.replace(b"+",b"") - tmp = int(tmp.decode("utf-8")) - if not reedsolo: - fin.seek(138) - ad = fin.read(tmp) - try: - ad = bytes(headerRsc.decode(ad)[0]) - except ReedSolomonError: - ad = b"Error decoding file metadata." - ad = ad.decode("utf-8") + fin.read(129) + metadataLength = fin.read(138) + metadataLength = bytes(rs128.decode(metadataLength)[0]) + metadataLength = metadataLength.replace(b"+",b"") + metadata = fin.read(int(metadataLength.decode("utf-8"))) + metadata = bytes(rs128.decode(metadata)[0]).decode("utf-8") + metadataString.set("File metadata (read only):") + metadataInput["state"] = "normal" + metadataInput.delete("1.0",tkinter.END) + metadataInput.insert("1.0",metadata) + metadataInput["state"] = "disabled" fin.close() - - # Insert the metadata into its text box - adArea["state"] = "normal" - adArea.delete("1.0",tkinter.END) - adArea.insert("1.0",ad) - adArea["state"] = "disabled" + + # Insert filename into output box + outputFrame.config(width=440) + outputInput["state"] = "normal" + outputInput.delete(0,tkinter.END) + outputInput.insert(0,inputFile[:-4]) + + # Update UI + setDecryptionUI() + else: + mode = "encrypt" # Update UI - adLabelString.set("File metadata (read only):") - keepBtn["state"] = "normal" - eraseBtn["state"] = "disabled" - rsBtn["state"] = "disabled" - cpasswordInput["state"] = "normal" - cpasswordInput.delete(0,"end") - cpasswordInput["state"] = "disabled" - cpasswordString.set("Confirm password (N/A):") - else: - # Update the UI - eraseBtn["state"] = "normal" - keepBtn["state"] = "disabled" - rsBtn["state"] = "normal" - adArea["state"] = "normal" - adArea.delete("1.0",tkinter.END) + setEncryptionUI() + + # Update output box with appropriate name + if inputFile: + outputInput.insert(0,inputFile) + else: + if onlyFiles: + tmp = Path(onlyFiles[0]).parent.absolute() + else: + tmp = Path(onlyFolders[0]).parent.absolute() + tmp = pathJoin(tmp,"Encrypted.zip") + tmp = tmp.replace("\\","/") + outputInput.insert(0,tmp) suffix = " (will encrypt)" - adLabelString.set(adString) - cpasswordInput["state"] = "normal" - cpasswordInput.delete(0,"end") - cpasswordString.set("Confirm password:") - cpasswordLabel["state"] = "normal" - adLabel["state"] = "normal" - - nFiles = len(files) - nFolders = len(draggedFolderPaths) + + nFiles = len(onlyFiles) + nFolders = len(onlyFolders) # Show selected file(s) and folder(s) - if (allFiles or files) and not draggedFolderPaths: + if (allFiles or onlyFiles) and not onlyFolders: inputString.set(f"{nFiles} files selected (will encrypt).") - elif draggedFolderPaths and not files: + elif onlyFolders and not onlyFiles: inputString.set(f"{nFolders} folder{'s' if nFolders!=1 else ''} selected (will encrypt).") - elif draggedFolderPaths and (allFiles or files): + elif onlyFolders and (allFiles or onlyFiles): inputString.set( f"{nFiles} file{'s' if nFiles!=1 else ''} and "+ f"{nFolders} folder{'s' if nFolders!=1 else ''} selected (will encrypt)." ) else: inputString.set(inputFile.split("/")[-1]+suffix) - - # Enable password box, etc. - passwordInput["state"] = "normal" - passwordInput.delete(0,"end") - passwordLabel["state"] = "normal" - startBtn["state"] = "normal" - statusString.set("Ready.") - status["state"] = "enabled" - progress["value"] = 0 - - # File decode error + + # UTF-8 decode error except UnicodeDecodeError: - statusString.set(corruptedNotice) + statusString.set(strings[20]) progress["value"] = 100 - - # No file(s) selected, do nothing + + # Nothing happened except: - inputString.set("Drag and drop file(s) and folder(s) into this window.") - resetUI() - - # Focus the dummy button to remove ugly borders - finally: - dummy.focus() - working = False - -# Clears the selected files -def clearInputs(): - dummy.focus() - resetUI() - -# Allow drag and drop + pass + +# Bind drag and drop to window def onDrop(e): global working if not working: - inputSelected(e.data) + filesDragged(e.data) + clearInput["state"] = "normal" + clearInput.config(cursor="hand2") tk.drop_target_register(DND_FILES) tk.dnd_bind("<>",onDrop) -# Label that displays selected input file -inputString = tkinter.StringVar(tk) -inputString.set("Drag and drop file(s) and folder(s) into this window.") -selectedInput = tkinter.ttk.Label( - tk, - textvariable=inputString -) -selectedInput.config(background="#ffffff") -selectedInput.place(x=17,y=16) - -# Clear input files -clearInput = tkinter.ttk.Button( - tk, - text="Clear", - command=clearInputs -) -if platform.system()=="Darwin": - clearInput.place(x=398,y=14,width=64,height=24) -else: - clearInput.place(x=421,y=14,width=40,height=24) - -separator = tkinter.ttk.Separator( - tk -) -separator.place(x=20,y=36,width=440) - -# Label that prompts user to enter a password -passwordString = tkinter.StringVar(tk) -passwordString.set("Password:") -passwordLabel = tkinter.ttk.Label( - tk, - textvariable=passwordString -) -passwordLabel.place(x=17,y=46) -passwordLabel.config(background="#ffffff") -passwordLabel["state"] = "disabled" - -# A frame to make password input fill width -passwordFrame = tkinter.Frame( - tk, - width=(445 if platform.system()=="Darwin" else 440), - height=22 -) -passwordFrame.place(x=(17 if platform.system()=="Darwin" else 20),y=66) -passwordFrame.columnconfigure(0,weight=10) -passwordFrame.grid_propagate(False) -# Password input box -passwordInput = tkinter.ttk.Entry( - passwordFrame, - show="\u2022" -) -passwordInput.grid(sticky="nesw") -passwordInput["state"] = "disabled" - -cpasswordString = tkinter.StringVar(tk) -cpasswordString.set("Confirm password:") -cpasswordLabel = tkinter.ttk.Label( - tk, - textvariable=cpasswordString -) -cpasswordLabel.place(x=17,y=96) -cpasswordLabel.config(background="#ffffff") -cpasswordLabel["state"] = "disabled" - -# A frame to make confirm password input fill width -cpasswordFrame = tkinter.Frame( - tk, - width=(445 if platform.system()=="Darwin" else 440), - height=22 -) -cpasswordFrame.place(x=(17 if platform.system()=="Darwin" else 20),y=116) -cpasswordFrame.columnconfigure(0,weight=10) -cpasswordFrame.grid_propagate(False) -# Confirm password input box -cpasswordInput = tkinter.ttk.Entry( - cpasswordFrame, - show="\u2022" -) -cpasswordInput.grid(sticky="nesw") -cpasswordInput["state"] = "disabled" - -# Start the encryption/decryption process -def start(): - global inputFile,outputFile,password,ad,kept - global working,gMode,headerRsc,allFiles,files - global dragFolderPath - dummy.focus() - reedsolo = False - chunkSize = 2**20 - - # Decide if encrypting or decrypting - if not inputFile.endswith(".pcv"): - mode = "encrypt" - gMode = "encrypt" - outputFile = inputFile+".pcv" - reedsolo = rs.get()==1 - else: - mode = "decrypt" - gMode = "decrypt" - # Check if Reed-Solomon was enabled by checking for "+" - test = open(inputFile,"rb") - decider = test.read(1).decode("utf-8") - test.close() - if decider=="+": - reedsolo = True - # Decrypted output is just input file without the extension - outputFile = inputFile[:-4] - - # Check if file already exists (getsize() throws error if file not found) - try: - getsize(outputFile) - force = messagebox.askyesno("Confirmation",overwriteNotice) - dummy.focus() - if force!=1: - return - except: - pass - - # Disable inputs and buttons while encrypting/decrypting +def work(): + global inputFile,outputFile,working,mode,rs13,rs128,reedsolo + global done,stopUpdating,startTime,previousTime,onlyFiles + global onlyFolders,allFiles,reedsoloFixed,reedsoloErrors disableAllInputs() + dummy.focus() + # Set and get some variables + kept = False + shouldKeep = keep.get()==1 + shouldErase = erase.get()==1 + reedsolo = rs.get()==1 + working = True + stopUpdating = False + headerBroken = False + reedsoloFixed = 0 + reedsoloErrors = 0 + password = passwordInput.get().encode("utf-8") + metadata = metadataInput.get("1.0",tkinter.END).encode("utf-8") + + # Decide if encrypting or decrypting + if mode=="encrypt": + outputFile = outputInput.get()+".pcv" + else: + outputFile = outputInput.get() + # Make sure passwords match - if passwordInput.get()!=cpasswordInput.get() and mode=="encrypt": - resetEncryptionUI() + if passwordInput.get()!=cPasswordInput.get() and mode=="encrypt": + setEncryptionUI() statusString.set("Passwords don't match.") return - + # Set progress bar indeterminate progress.config(mode="indeterminate") progress.start(15) - statusString.set(rscNotice) - # Create Reed-Solomon object - if reedsolo: - # 13 bytes per 128 bytes, ~10% larger output file - rsc = RSCodec(13) - - # Compress files together if user dragged multiple files - if allFiles or files: - statusString.set(compressingNotice) - tmp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - if files: - zfPath = Path(files[0]).parent.absolute() + # Compress files together if necessary + if onlyFiles or allFiles: + statusString.set(strings[1]) + tmp = outputFile[:-4] + if onlyFiles: + zfPath = Path(onlyFiles[0]).parent.absolute() else: zfPath = Path(dirname(allFiles[0])).parent.absolute() zfOffset = len(str(zfPath)) - zfName = pathJoin(zfPath,tmp+".zip") + zfName = pathJoin(zfPath,tmp) zf = ZipFile(zfName,"w") for i in allFiles: zf.write(i,i[zfOffset:]) - for i in files: + for i in onlyFiles: zf.write(i,pathSplit(i)[1]) - + zf.close() inputFile = zfName outputFile = zfName+".pcv" outputPath = dirname(outputFile) - - # Set and get some variables - working = True - headerBroken = False - reedsoloFixedCount = 0 - reedsoloErrorCount = 0 - dummy.focus() - password = passwordInput.get().encode("utf-8") - ad = adArea.get("1.0",tkinter.END).encode("utf-8") - wipe = erase.get()==1 - + # Open files try: fin = open(inputFile,"rb") except: - resetEncryptionUI() - statusString.set("Folder is empty.") + setEncryptionUI() + statusString.set(strings[16]) return - - if reedsolo and mode=="decrypt": - # Move pointer one forward - fin.read(1) - fout = open(outputFile,"wb+") - if reedsolo and mode=="encrypt": - # Signal that Reed-Solomon was enabled with a "+" - fout.write(b"+") - - # Generate values for encryption if encrypting + if mode=="encrypt": salt = urandom(16) nonce = urandom(24) + fout = open(outputFile,"wb+") + if reedsolo: + fout.write(rs128.encode(b"+")) + else: + fout.write(rs128.encode(b"-")) - # Reed-Solomon-encode metadata - ad = bytes(headerRsc.encode(ad)) - # Write the metadata to output - tmp = str(len(ad)).encode("utf-8") - # Right-pad with "+" - while len(tmp)!=10: - tmp += b"+" - tmp = bytes(headerRsc.encode(tmp)) - fout.write(tmp) # Length of metadata - fout.write(ad) # Metadata (associated data) - - # Write zeros as placeholders, come back to write over it later. - # Note that 128 extra Reed-Solomon bytes are added - fout.write(b"0"*192) # SHA3-512 of encryption key - fout.write(b"0"*192) # CRC of file - fout.write(b"0"*144) # Poly1305 tag - # Reed-Solomon-encode salt and nonce - fout.write(bytes(headerRsc.encode(salt))) # Argon2 salt - fout.write(bytes(headerRsc.encode(nonce))) # ChaCha20 nonce - - # If decrypting, read values from file + metadata = rs128.encode(metadata) + tmp = len(metadata) + tmp = f"{tmp:+<10}" + tmp = rs128.encode(tmp.encode("utf-8")) + + fout.write(tmp) + fout.write(metadata) + fout.write(rs128.encode(salt)) # Argon2 salt + fout.write(rs128.encode(nonce)) # ChaCha20 nonce + fout.write(b"0"*192) # Hash of key + fout.write(b"0"*144) # Poly1305 MAC + fout.write(b"0"*192) # BLAKE2b CRC else: - # Move past metadata into actual data - tmp = fin.read(138) - if tmp[0]==43: - tmp = tmp[1:]+fin.read(1) - tmp = bytes(headerRsc.decode(tmp)[0]) - tmp = tmp.replace(b"+",b"") - adlen = int(tmp.decode("utf-8")) - fin.read(int(adlen)) + tmp = fin.read(129) + if bytes(rs128.decode(tmp)[0])==b"+": + reedsolo = True + else: + reedsolo = False + + metadataLength = fin.read(138) + metadataLength = bytes(rs128.decode(metadataLength)[0]) + metadataLength = metadataLength.replace(b"+",b"") + fin.read(int(metadataLength.decode("utf-8"))) - # Read the salt, nonce, etc. - cs = fin.read(192) - crccs = fin.read(192) - digest = fin.read(144) salt = fin.read(144) nonce = fin.read(152) - # Reed-Solomon-decode each value + keycs = fin.read(192) + maccs = fin.read(144) + crccs = fin.read(192) + try: - cs = bytes(headerRsc.decode(cs)[0]) + salt,_,fixed = rs128.decode(salt) + salt = bytes(salt) + reedsoloFixed += len(fixed) except: headerBroken = True - cs = cs[:64] try: - crccs = bytes(headerRsc.decode(crccs)[0]) + nonce,_,fixed = rs128.decode(nonce) + nonce = bytes(nonce) + reedsoloFixed += len(fixed) except: headerBroken = True - crccs = crccs[:64] try: - digest = bytes(headerRsc.decode(digest)[0]) + keycs,_,fixed = rs128.decode(keycs) + keycs = bytes(keycs) + reedsoloFixed += len(fixed) except: headerBroken = True - digest = digest[:16] try: - salt = bytes(headerRsc.decode(salt)[0]) + maccs,_,fixed = rs128.decode(maccs) + maccs = bytes(maccs) + reedsoloFixed += len(fixed) except: headerBroken = True - salt = salt[:16] try: - nonce = bytes(headerRsc.decode(nonce)[0]) + crccs,_,fixed = rs128.decode(crccs) + crccs = bytes(crccs) + reedsoloFixed += len(fixed) except: headerBroken = True - nonce = nonce[:24] - + if headerBroken: - if keep.get()!=1: - statusString.set(veryCorruptedNotice) + if not shouldKeep: + statusString.set(strings[8]) fin.close() fout.close() remove(outputFile) - # Reset UI - resetDecryptionUI() + setDecryptionUI() return else: kept = "badlyCorrupted" - - # Show notice about key derivation - statusString.set(derivingNotice) - - # Derive argon2id key + + statusString.set(strings[9]) + key = hash_secret_raw( password, salt, - time_cost=8, # 8 iterations - memory_cost=2**20, # 2^20 Kibibytes (1GiB) - parallelism=8, # 8 parallel threads + time_cost=8, + memory_cost=2**10, + parallelism=8, hash_len=32, - type=Type.ID + type=argonType.D ) - - # Key deriving done, set progress bar determinate + progress.stop() progress.config(mode="determinate") progress["value"] = 0 - - # Compute hash of derived key - check = sha3_512.new() - check.update(key) - check = check.digest() - - # If decrypting, check if key is correct + + check = SHA3_512.new(data=key).digest() + if mode=="decrypt": - # If key is incorrect... - if not compare_digest(check,cs): + if not compare_digest(check,keycs): if not headerBroken: - statusString.set(passwordNotice) + statusString.set(strings[2]) fin.close() - fout.close() - remove(outputFile) - # Reset UI - resetDecryptionUI() + setDecryptionUI() return - - # Create XChaCha20-Poly1305 object + fout = open(outputFile,"wb+") + + crc = BLAKE2b.new(digest_bits=512) cipher = ChaCha20_Poly1305.new(key=key,nonce=nonce) - # Cyclic redundancy check for file corruption - crc = sha3_512.new() - - # Amount of data encrypted/decrypted, total file size, starting time done = 0 total = getsize(inputFile) - - # If secure wipe enabled, create a wiper object - - # Keep track of time because it flies... startTime = datetime.now() previousTime = datetime.now() - # Continously read file in chunks of 1MB + Thread(target=updateStats,daemon=True,args=(total,)).start() while True: if mode=="decrypt" and reedsolo: - # Read a chunk plus Reed-Solomon recovery bytes piece = fin.read(1104905) else: - piece = fin.read(chunkSize) - - # If EOF + piece = fin.read(2**20) if not piece: - if mode=="encrypt": - # Get the cipher MAC tag (Poly1305) - digest = cipher.digest() - fout.flush() - fout.close() - fout = open(outputFile,"r+b") - # Compute the offset and seek to it (unshift "+") - rsOffset = 1 if reedsolo else 0 - fout.seek(138+len(ad)+rsOffset) - # Write hash of key, CRC, and Poly1305 MAC tag - fout.write(bytes(headerRsc.encode(check))) - fout.write(bytes(headerRsc.encode(crc.digest()))) - fout.write(bytes(headerRsc.encode(digest))) - else: - # If decrypting, verify CRC - crcdg = crc.digest() - if not compare_digest(crccs,crcdg): - # File is corrupted - statusString.set(corruptedNotice) - progress["value"] = 100 - fin.close() - fout.close() - # If keep file not checked... - if keep.get()!=1: - remove(outputFile) - # Reset UI - resetDecryptionUI() - del fin,fout,cipher,key - return - else: - if not kept: - kept = "corrupted" - # Next, verify MAC tag (Poly1305) - try: - # Throws ValueError if incorrect Poly1305 - cipher.verify(digest) - except: - if not reedsoloErrorCount and not headerBroken: - # File is modified - statusString.set(modifiedNotice) - progress["value"] = 100 - fin.close() - fout.close() - # If keep file not checked... - if keep.get()!=1: - remove(outputFile) - # Reset UI - resetDecryptionUI() - del fin,fout,cipher,key - return - else: - if not kept: - kept = "modified" break - - # Encrypt/decrypt chunk and update CRC + if mode=="encrypt": - # Encrypt piece data = cipher.encrypt(piece) - # Update checksum - crc.update(data) if reedsolo: - # Encode using Reed-Solomon if user chooses - data = bytes(rsc.encode(data)) + data = bytes(rs13.encode(data)) + crc.update(data) else: - # Basically encrypting but in reverse + crc.update(piece) if reedsolo: try: - data,_,fixed = rsc.decode(piece) + data,_,fixed = rs13.decode(piece) except ReedSolomonError: # File is really corrupted - if not reedsoloErrorCount: - if keep.get()!=1: - statusString.set(veryCorruptedNotice) - progress["value"] = 100 - # If keep file not checked... - if keep.get()!=1: + if not reedsoloErrors and not shouldKeep: + statusString.set(strings[8]) + + if not shouldKeep: fin.close() fout.close() remove(outputFile) - # Reset UI - resetDecryptionUI() - del fin,fout,cipher,key + setDecryptionUI() return - else: - kept = "badlyCorrupted" - # Attempt to recover badly corrupted data - data = b"" - piece = piece[:-13] - counter = 0 - while True: - # Basically just strip the Reed-Solomon bytes - # and return the original non-encoded data - if counter<1104905: - data += piece[counter:counter+242] - counter += 255 # 255 bytes, 242 original - else: - break - fixed = bytearray() - reedsoloErrorCount += 1 - data = bytes(data) - reedsoloFixedCount += len(fixed) - crc.update(data) + + kept = "badlyCorrupted" + # Attempt to recover badly corrupted data + data = b"" + piece = piece[:-13] + counter = 0 + while True: + # Basically just strip the Reed-Solomon bytes + # and return the original non-encoded data + if counter<1104905: + data += piece[counter:counter+242] + counter += 255 # 255 bytes, 242 original + else: + break + fixed = bytearray() + reedsoloErrors += 1 + + reedsoloFixed += len(fixed) data = cipher.decrypt(data) + else: - crc.update(piece) data = cipher.decrypt(piece) - - # Calculate speed, ETA, etc. - elapsed = (datetime.now()-previousTime).total_seconds() or 0.0001 - sinceStart = (datetime.now()-startTime).total_seconds() or 0.0001 - previousTime = datetime.now() - - percent = done*100/total - progress["value"] = percent - - speed = (done/sinceStart)/10**6 or 0.0001 - eta = round((total-done)/(speed*10**6)) - - # Seconds to minutes if seconds more than 59 - if eta>=60: - # Set blank ETA if just starting - if sinceStart<0.5: - eta = "..." - else: - eta = f"{eta//60}m {eta%60}" - if isinstance(eta,int) or isinstance(eta,float): - if eta<0: - eta = 0 - - # Update status - info = f"{percent:.0f}% at {speed:.2f} MB/s (ETA: {eta}s)" - - if reedsolo and mode=="decrypt" and reedsoloFixedCount: - tmp = "s" if reedsoloFixedCount!=1 else "" - info += f", fixed {reedsoloFixedCount} corrupted byte{tmp}" - if reedsolo and mode=="decrypt" and reedsoloErrorCount: - info += f", {reedsoloErrorCount} MB unrecoverable" - - statusString.set(info) - - # Increase done and write to output - done += 1104905 if (reedsolo and mode=="decrypt") else chunkSize + fout.write(data) - + done += 2**20 + + if mode=="encrypt": + fout.flush() + fout.close() + fout = open(outputFile,"r+b") + fout.seek(129+138+len(metadata)+144+152) + fout.write(rs128.encode(check)) + fout.write(rs128.encode(cipher.digest())) + fout.write(rs128.encode(crc.digest())) + else: + if not compare_digest(crccs,crc.digest()): + statusString.set(strings[3]) + progress["value"] = 100 + fin.close() + fout.close() + + if keep.get()!=1: + remove(outputFile) + setDecryptionUI() + return + else: + if not kept: + kept = "corrupted" + + try: + cipher.verify(maccs) + except: + if not reedsoloErrors and not headerBroken: + # File is modified + statusString.set(modifiedNotice) + progress["value"] = 100 + fin.close() + fout.close() + # If keep file not checked... + if keep.get()!=1: + remove(outputFile) + # Reset UI + setDecryptionUI() + return + else: + if not kept: + kept = "modified" + # Flush outputs, close files if not kept: fout.flush() fsync(fout.fileno()) fout.close() fin.close() + stopUpdating = True # Securely wipe files as necessary - if wipe: - if draggedFolderPaths: - for i in draggedFolderPaths: + if shouldErase: + if onlyFolders: + for i in onlyFolders: secureWipe(i) - if files: - for i in range(len(files)): - statusString.set(erasingNotice+f" ({i}/{len(files)}") - progress["value"] = i/len(files) - secureWipe(files[i]) + if onlyFiles: + for i in range(len(onlyFiles)): + statusString.set(strings[12]+f" ({i}/{len(onlyFiles)}") + progress["value"] = i/len(onlyFiles) + secureWipe(onlyFiles[i]) secureWipe(inputFile) + # Secure wipe not enabled else: - if allFiles: + if allFiles or onlyFiles: # Remove temporary zip file if created remove(inputFile) + print(kept,reedsoloFixed) # Show appropriate notice if file corrupted or modified if not kept: statusString.set(f"Completed. (Click here to show output)") - # Show Reed-Solomon stats if it fixed corrupted bytes - if mode=="decrypt" and reedsolo and reedsoloFixedCount: + if mode=="decrypt" and reedsoloFixed: statusString.set( - f"Completed with {reedsoloFixedCount}"+ - f" bytes fixed. (Output: {output})" + f"Completed with {reedsoloFixed}"+ + f" bytes fixed. (Click here to show output)" ) else: if kept=="modified": - statusString.set(kModifiedNotice) + statusString.set(strings[7]) elif kept=="corrupted": - statusString.set(kCorruptedNotice) + statusString.set(strings[6]) else: - statusString.set(kVeryCorruptedNotice) + statusString.set(strings[8]) status.config(cursor="hand2") @@ -809,49 +898,49 @@ def start(): status.bind("", lambda e:showOutput(output) ) + # Reset variables and UI states resetUI() - status["state"] = "normal" inputFile = "" outputFile = "" - password = "" - ad = "" - kept = False + allFiles = [] + onlyFolders = [] + onlyFiles = [] working = False - allFiles = False - dragFolderPath = False - - # Wipe keys for safety - del fin,fout,cipher,key -# Wraps the start() function with error handling -def wrapper(): - global working,gMode - # Try start() and handle errors - try: - start() - except: - # Reset UI accordingly +def updateStats(total): + global startTime,previousTime,done,stopUpdating,reedsolo,reedsoloFixed,reedsoloErrors + while True: + validStatus = ( + statusString.get().startswith("Working") or statusString.get().startswith("Deriving") + ) + if not stopUpdating and validStatus: + elapsed = (datetime.now()-previousTime).total_seconds() or 0.0001 + sinceStart = (datetime.now()-startTime).total_seconds() or 0.0001 + previousTime = datetime.now() + percent = done*100/total + progress["value"] = percent + + speed = (done/sinceStart)/10**6 or 0.0001 + eta = round((total-done)/(speed*10**6)) + + info = f"Working... {min(percent,100):.0f}% at {speed:.2f} MB/s (ETA: {max(eta,0)}s)" - if gMode=="decrypt": - resetDecryptionUI() + if reedsolo and mode=="decrypt" and reedsoloFixed: + tmp = "s" if reedsoloFixed!=1 else "" + info += f", fixed {reedsoloFixed} corrupted byte{tmp}" + + if reedsolo and mode=="decrypt" and reedsoloErrors: + info += f", {reedsoloErrors} MB unrecoverable" + + statusString.set(info) + sleep(0.05) else: - resetEncryptionUI() + sys.exit(0) + break - statusString.set(unknownErrorNotice) - dummy.focus() - finally: - sys.exit(0) - -# Encryption/decrypt is done is a separate thread so the UI -# isn't blocked. This is a wrapper to spawn a thread and start it. -def startWorker(): - thread = Thread(target=wrapper,daemon=True) - thread.start() - -# Securely wipe file def secureWipe(fin): - statusString.set(erasingNotice) + statusString.set(strings[12]) # Check platform, erase accordingly if platform.system()=="Windows": if isdir(fin): @@ -860,13 +949,13 @@ def secureWipe(fin): if dirname(i) not in paths: paths.append(dirname(i)) for i in range(len(paths)): - statusString.set(erasingNotice+f" ({i}/{len(paths)})") + statusString.set(strings[12]+f" ({i}/{len(paths)})") progress["value"] = 100*i/len(paths) system(f'cd "{paths[i]}" && "{rootDir}/sdelete64.exe" * -p 4 -s -nobanner') system(f'cd "{rootDir}"') rmtree(fin) else: - statusString.set(erasingNotice) + statusString.set(strings[12]) progress["value"] = 100 system(f'sdelete64.exe "{fin}" -p 4 -nobanner') elif platform.system()=="Darwin": @@ -874,251 +963,112 @@ def secureWipe(fin): else: system(f'shred -uz "{fin}" -n 4') -# Disable all inputs while encrypting/decrypting -def disableAllInputs(): - passwordInput["state"] = "disabled" - cpasswordInput["state"] = "disabled" - adArea["state"] = "disabled" - startBtn["state"] = "disabled" - eraseBtn["state"] = "disabled" - keepBtn["state"] = "disabled" - rsBtn["state"] = "disabled" - -# Reset UI to encryption state -def resetEncryptionUI(): - global working - passwordInput["state"] = "normal" - cpasswordInput["state"] = "normal" - adArea["state"] = "normal" - startBtn["state"] = "normal" - eraseBtn["state"] = "normal" - rsBtn["state"] = "normal" - working = False - progress.stop() - progress.config(mode="determinate") - progress["value"] = 100 - -# Reset UI to decryption state -def resetDecryptionUI(): - global working - passwordInput["state"] = "normal" - adArea["state"] = "normal" - startBtn["state"] = "normal" - keepBtn["state"] = "normal" - working = False - progress.stop() - progress.config(mode="determinate") - progress["value"] = 100 - -# Reset UI to original state (no file selected) +# Reset UI to state where no files are selected def resetUI(): - adArea["state"] = "normal" - adArea.delete("1.0",tkinter.END) - adArea["state"] = "disabled" - adLabel["state"] = "disabled" - startBtn["state"] = "disabled" + inputString.set(strings[18]) + inputLabel["state"] = "normal" + clearInput["state"] = "disabled" + clearInput.config(cursor="") + outputLabel["state"] = "disabled" + outputFrame.config(width=440) + outputInput["state"] = "normal" + outputInput.delete(0,"end") + outputInput["state"] = "disabled" + passwordLabel["state"] = "disabled" passwordInput["state"] = "normal" passwordInput.delete(0,"end") passwordInput["state"] = "disabled" - passwordLabel["state"] = "disabled" - cpasswordInput["state"] = "normal" - cpasswordInput.delete(0,"end") - cpasswordInput["state"] = "disabled" - cpasswordString.set("Confirm password:") - cpasswordLabel["state"] = "disabled" - status["state"] = "disabled" - progress["value"] = 0 - inputString.set("Drag and drop file(s) and folder(s) into this window.") - keepBtn["state"] = "normal" + cPasswordString.set("Confirm password:") + cPasswordLabel["state"] = "disabled" + cPasswordInput["state"] = "normal" + cPasswordInput.delete(0,"end") + cPasswordInput["state"] = "disabled" + metadataFrame.config(bg="#e5eaf0") + metadataInput.config(bg="#fbfcfc") + metadataInput.config(fg="#000000") + metadataString.set(strings[0]) + metadataLabel["state"] = "disabled" + metadataInput["state"] = "normal" + metadataInput.delete("1.0",tkinter.END) + metadataInput["state"] = "disabled" keep.set(0) keepBtn["state"] = "disabled" - eraseBtn["state"] = "normal" erase.set(0) eraseBtn["state"] = "disabled" rs.set(0) rsBtn["state"] = "disabled" + startBtn["state"] = "disabled" + cancelBtn["state"] = "disabled" + progress.stop() + progress.config(mode="determinate") + progress["value"] = 0 + dummy.focus() + +# Set UI to encryption state +def setEncryptionUI(): + outputLabel["state"] = "normal" + outputInput["state"] = "normal" + outputFrame.config(width=410) + passwordLabel["state"] = "normal" + passwordInput["state"] = "normal" + cPasswordLabel["state"] = "normal" + cPasswordString.set("Confirm password:") + cPasswordInput["state"] = "normal" + metadataFrame.config(bg="#d8ddea") + metadataInput.config(bg="#ffffff") + metadataInput.config(fg="#000000") + metadataLabel["state"] = "normal" + metadataInput["state"] = "normal" + eraseBtn["state"] = "normal" + rsBtn["state"] = "normal" + startBtn["state"] = "normal" progress.stop() progress.config(mode="determinate") progress["value"] = 0 -def showOutput(file): - if platform.system()=="Windows": - system(f'explorer /select,"{file}"') - elif platform.system()=="Darwin": - system(f'cd "{dirname(file)}"; open -R {pathSplit(file)[1]}') - system(f'cd "{rootDir}"') - else: - system(f'xdg-open "{dirname(file)}"') +# Set UI to decryption state +def setDecryptionUI(): + outputLabel["state"] = "normal" + outputInput["state"] = "normal" + outputFrame.config(width=440) + passwordLabel["state"] = "normal" + passwordInput["state"] = "normal" + cPasswordString.set("Confirm password (N/A):") + metadataFrame.config(bg="#e5eaf0") + metadataInput.config(bg="#fbfcfc") + metadataInput.config(fg="#666666") + metadataString.set(strings[19]) + metadataInput["state"] = "disabled" + keepBtn["state"] = "normal" + startBtn["state"] = "normal" + progress.stop() + progress.config(mode="determinate") + progress["value"] = 0 -# ad stands for "associated data"/metadata -adLabelString = tkinter.StringVar(tk) -adLabelString.set(adString) -adLabel = tkinter.ttk.Label( - tk, - textvariable=adLabelString -) -adLabel.place(x=17,y=148) -adLabel.config(background="#ffffff") -adLabel["state"] = "disabled" +# Disable all inputs while encrypting/decrypting +def disableAllInputs(): + clearInput["state"] = "disabled" + outputInput["state"] = "disabled" + passwordInput["state"] = "disabled" + cPasswordInput["state"] = "disabled" + cPasswordString.set("Confirm password:") + metadataFrame.config(bg="#e5eaf0") + metadataInput.config(bg="#fbfcfc") + metadataInput.config(fg="#666666") + metadataInput["state"] = "disabled" + startBtn["state"] = "disabled" + eraseBtn["state"] = "disabled" + keepBtn["state"] = "disabled" + rsBtn["state"] = "disabled" -# Frame so metadata text box can fill width -adFrame = tkinter.Frame( - tk, - width=440, - height=100 -) -adFrame.place(x=20,y=168) -adFrame.columnconfigure(0,weight=10) -adFrame.grid_propagate(False) - -# Metadata text box -adArea = tkinter.Text( - adFrame, - exportselection=0 -) -adArea.config(font=("Consolas",12)) -adArea.grid(sticky="we") -adArea["state"] = "disabled" - -# Check box for keeping corrupted/modified output -keep = tkinter.IntVar() -keepBtn = tkinter.ttk.Checkbutton( - tk, - text=keepNotice, - variable=keep, - onvalue=1, - offvalue=0, - command=lambda:dummy.focus() -) -keepBtn.place(x=18,y=280) -keepBtn["state"] = "disabled" - -# Check box for securely erasing original file -erase = tkinter.IntVar() -eraseBtn = tkinter.ttk.Checkbutton( - tk, - text=eraseNotice, - variable=erase, - onvalue=1, - offvalue=0, - command=lambda:dummy.focus() -) -eraseBtn.place(x=18,y=300) -eraseBtn["state"] = "disabled" - -# Check box for Reed Solomon -rs = tkinter.IntVar() -rsBtn = tkinter.ttk.Checkbutton( - tk, - text=rsNotice, - variable=rs, - onvalue=1, - offvalue=0, - command=lambda:dummy.focus() -) -rsBtn.place(x=18,y=320) -rsBtn["state"] = "disabled" - -# Frame so start button can fill width -startFrame = tkinter.Frame( - tk, - width=442, - height=24 -) -startFrame.place(x=19,y=350) -startFrame.columnconfigure(0,weight=10) -startFrame.grid_propagate(False) -# Start button -startBtn = tkinter.ttk.Button( - startFrame, - text="Start", - command=startWorker -) -startBtn.grid(sticky="nesw") -startBtn["state"] = "disabled" - -# Progress bar -progress = tkinter.ttk.Progressbar( - tk, - orient=tkinter.HORIZONTAL, - length=440, - mode="determinate" -) -progress.place(x=20,y=378) - -# Status label -statusString = tkinter.StringVar(tk) -statusString.set("Ready.") -status = tkinter.ttk.Label( - tk, - textvariable=statusString -) -status.place(x=17,y=406) -status.config(background="#ffffff") -status["state"] = "disabled" - -# Credits :) -hint = "Created by Evan Su. Click for details and source." -creditsString = tkinter.StringVar(tk) -creditsString.set(hint) -credits = tkinter.ttk.Label( - tk, - textvariable=creditsString, - cursor="hand2" -) -credits["state"] = "disabled" -credits.config(background="#ffffff") -credits.place(x=17,y=436) -source = "https://github.com/HACKERALERT/Picocrypt" -credits.bind("",lambda e:webbrowser.open(source)) - -# Version -versionString = tkinter.StringVar(tk) -versionString.set("v1.11") -version = tkinter.ttk.Label( - tk, - textvariable=versionString -) -version["state"] = "disabled" -version.config(background="#ffffff") -version.place(x=(420 if platform.system()=="Darwin" else 430),y=436) - -# Dummy button to remove focus from other buttons -# and prevent ugly border highlighting -dummy = tkinter.ttk.Button( - tk -) -dummy.place(x=480,y=0) - -# Function to create Reed-Solomon header codec -def createRsc(): - global headerRsc - headerRsc = RSCodec(128) +def prepareRsc(): + global rs13,rs128 + rs13 = RSCodec(13) + rs128 = RSCodec(128) sys.exit(0) - -def prepare(): - if platform.system()=="Windows": - system("sdelete64.exe /accepteula") -# Close window only if not encrypting or decrypting -def onClose(): - global outputFile - if not working: - tk.destroy() - else: - force = messagebox.askyesno("Confirmation",cancelNotice) - if force: - tk.destroy() +# Prepare Reed-Solomon codecs +Thread(target=prepareRsc,daemon=True).start() -# Main application loop -if __name__=="__main__": - # Create Reed-Solomon header codec - tmp = Thread(target=createRsc,daemon=True) - tmp.start() - # Prepare application - tmp = Thread(target=prepare,daemon=True) - tmp.start() - # Start tkinter - tk.protocol("WM_DELETE_WINDOW",onClose) - tk.mainloop() - sys.exit(0) +# Start tkinter +tk.mainloop()