mirror of
https://github.com/HACKERALERT/Picocrypt.git
synced 2024-06-28 02:51:00 +12:00
1504 lines
38 KiB
Python
1504 lines
38 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
Picocrypt v1.12.1
|
|
Copyright (c) Evan Su (https://evansu.cc)
|
|
Released under a GNU GPL v3 License
|
|
https://github.com/HACKERALERT/Picocrypt
|
|
|
|
~ In cryptography we trust ~
|
|
|
|
"""
|
|
|
|
# Import dependencies
|
|
from threading import Thread
|
|
from datetime import datetime,timedelta
|
|
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
|
|
from blake3 import blake3
|
|
from hmac import compare_digest
|
|
from creedsolo import RSCodec,ReedSolomonError
|
|
from os import urandom,fsync,remove,system
|
|
from os.path import getsize,expanduser,isdir,exists
|
|
from os.path import basename,dirname,abspath,realpath
|
|
from os.path import join as pathJoin,split as pathSplit
|
|
from pathlib import Path
|
|
from zipfile import ZipFile
|
|
from shutil import rmtree
|
|
from tkinterdnd2 import TkinterDnD,DND_FILES
|
|
from tkinter.filedialog import asksaveasfilename
|
|
from ttkthemes import ThemedStyle
|
|
from time import sleep
|
|
import re
|
|
import sys
|
|
import tkinter
|
|
import tkinter.ttk
|
|
import tkinter.scrolledtext
|
|
import webbrowser
|
|
import platform
|
|
|
|
# Global variables
|
|
rootDir = dirname(realpath(__file__))
|
|
working = False
|
|
mode = False
|
|
inputFile = False
|
|
outputFile = False
|
|
rs128 = False
|
|
rs13 = False
|
|
allFiles = False
|
|
onlyFolders = False
|
|
onlyFiles = False
|
|
startTime = False
|
|
previousTime = False
|
|
done = False
|
|
stopUpdating = False
|
|
reedsolo = False
|
|
reedsoloFixed = False
|
|
reedsoloErrors = False
|
|
|
|
# A list of strings and notices
|
|
strings = [
|
|
"File metadata (optional):",
|
|
"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?",
|
|
"The input file's metadata is corrupted.",
|
|
"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.",
|
|
"Error. The input file is corrupted so badly that Picocrypt can't decrypt it."
|
|
]
|
|
|
|
# Create root window
|
|
tk = TkinterDnD.Tk()
|
|
tk.geometry("480x500")
|
|
tk.title("Picocrypt")
|
|
tk.resizable(0,0)
|
|
tk.configure(background="#f5f6f7")
|
|
|
|
# Set "arc" theme
|
|
ThemedStyle(tk).set_theme("arc")
|
|
|
|
# Disable high DPI on Windows to prevent ugly scaling
|
|
try:
|
|
from ctypes import windll
|
|
windll.shcore.SetProcessDpiAwareness(0)
|
|
except:
|
|
pass
|
|
|
|
# Try setting window icon if it exists
|
|
try:
|
|
# PyInstaller bundles files in _MEIPASS
|
|
from sys import _MEIPASS
|
|
favicon = tkinter.PhotoImage(file=pathJoin(_MEIPASS,"key.png"))
|
|
tk.iconphoto(False,favicon)
|
|
except:
|
|
try:
|
|
# PyInstaller not used, load from same directory
|
|
favicon = tkinter.PhotoImage(file="key.png")
|
|
tk.iconphoto(False,favicon)
|
|
except:
|
|
pass
|
|
|
|
# Dummy button used for removing ugly highlights
|
|
dummy = tkinter.ttk.Button(tk)
|
|
dummy.place(x=480,y=0)
|
|
|
|
# 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(),statusString.set("Ready.")]
|
|
)
|
|
clearInput.place(x=386,y=13,width=74,height=27)
|
|
clearInput["state"] = "disabled"
|
|
|
|
# Separator for aesthetics
|
|
tkinter.Frame(
|
|
tk,
|
|
bg="#dfe3ed",
|
|
height=1
|
|
).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=51)
|
|
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=314,y=71)
|
|
|
|
# A frame to allow output box to fill width
|
|
outputFrame = tkinter.Frame(
|
|
tk,
|
|
width=320,
|
|
height=24
|
|
)
|
|
outputFrame.place(x=20,y=69)
|
|
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"
|
|
|
|
# A "or" label between output box and "Save as" button
|
|
orString = tkinter.StringVar(tk)
|
|
orString.set("or")
|
|
orLabel = tkinter.ttk.Label(
|
|
tk,
|
|
textvariable=orString
|
|
)
|
|
orLabel.place(x=356,y=71)
|
|
orLabel["state"] = "disabled"
|
|
|
|
# Save as custom file
|
|
def saveAs():
|
|
global mode,onlyFiles,onlyFolders
|
|
dummy.focus()
|
|
|
|
# Get the root directory to ask user to save as
|
|
if inputFile:
|
|
saveDir = dirname(inputFile)
|
|
elif onlyFiles:
|
|
saveDir = dirname(onlyFiles[0])
|
|
else:
|
|
saveDir = Path(onlyFolders[0]).parent.absolute()
|
|
|
|
# Ask user to save file
|
|
tmp = asksaveasfilename(
|
|
initialdir=saveDir,
|
|
initialfile=(
|
|
basename(inputFile)[:-4] if mode=="decrypt" else basename(inputFile)+".pcv"
|
|
),
|
|
confirmoverwrite=True
|
|
)
|
|
|
|
# If a custom output is chosen, update the output box accordingly
|
|
if tmp:
|
|
outputInput.delete(0,tkinter.END)
|
|
outputInput.insert(0,(tmp if mode=="decrypt" else tmp[:-4]))
|
|
|
|
# Button to allow custom output
|
|
saveAsBtn = tkinter.ttk.Button(
|
|
tk,
|
|
text="Save as",
|
|
command=saveAs
|
|
)
|
|
saveAsBtn.place(x=386,y=67,width=74,height=27)
|
|
saveAsBtn["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=103)
|
|
passwordLabel["state"] = "disabled"
|
|
|
|
# Allow password input to fill width
|
|
passwordFrame = tkinter.Frame(
|
|
tk,
|
|
width=210,
|
|
height=24
|
|
)
|
|
passwordFrame.place(x=20,y=121)
|
|
passwordFrame.columnconfigure(0,weight=10)
|
|
passwordFrame.grid_propagate(False)
|
|
|
|
# Password input box
|
|
passwordInput = tkinter.ttk.Entry(
|
|
passwordFrame,
|
|
show="•"
|
|
)
|
|
passwordInput.grid(sticky="nesw")
|
|
passwordInput["state"] = "disabled"
|
|
|
|
# Toggle password visibility
|
|
def showPassword(e):
|
|
if passwordInput.cget("show"):
|
|
passwordInput.config(show="")
|
|
cPasswordInput.config(show="")
|
|
else:
|
|
passwordInput.config(show="•")
|
|
cPasswordInput.config(show="•")
|
|
|
|
# Eye icon to show password
|
|
passwordShowString = tkinter.StringVar(tk)
|
|
passwordShowString.set((" " if platform.system()=="Darwin" else "")+"👁")
|
|
passwordShow = tkinter.ttk.Label(
|
|
tk,
|
|
textvariable=passwordShowString,
|
|
cursor="hand2",
|
|
font=("TkDefaultFont",14)
|
|
)
|
|
passwordShow.bind("<Button-1>",showPassword)
|
|
passwordShow["state"] = "disabled"
|
|
passwordShow.place(
|
|
x=(235 if platform.system()=="Windows" else 230),
|
|
y=(121 if platform.system()=="Windows" else 124)
|
|
)
|
|
|
|
# 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=158)
|
|
cPasswordLabel["state"] = "disabled"
|
|
|
|
# Allow confirm password input to fill width
|
|
cPasswordFrame = tkinter.Frame(
|
|
tk,
|
|
width=210,
|
|
height=24
|
|
)
|
|
cPasswordFrame.place(x=20,y=176)
|
|
cPasswordFrame.columnconfigure(0,weight=10)
|
|
cPasswordFrame.grid_propagate(False)
|
|
|
|
# Confirm password input box
|
|
cPasswordInput = tkinter.ttk.Entry(
|
|
cPasswordFrame,
|
|
show="•"
|
|
)
|
|
cPasswordInput.grid(sticky="nesw")
|
|
cPasswordInput["state"] = "disabled"
|
|
|
|
# Show strength of password
|
|
def showStrength():
|
|
global mode
|
|
if mode=="decrypt":
|
|
return
|
|
password = passwordInput.get()
|
|
containsLetters = any(i.isalpha() for i in password)
|
|
containsNumbers = any(i.isdigit() for i in password)
|
|
containsSymbols = any(not i.isalnum() for i in password)
|
|
longEnough = len(password)>8
|
|
if containsLetters and containsNumbers and containsSymbols and longEnough:
|
|
passwordStrength.config(width=208)
|
|
passwordStrength.config(bg="#149414")
|
|
elif containsLetters and containsNumbers and containsSymbols:
|
|
passwordStrength.config(width=140)
|
|
passwordStrength.config(bg="#fada52")
|
|
elif containsLetters and containsNumbers or \
|
|
(containsLetters and containsSymbols) or \
|
|
(containsNumbers and containsSymbols):
|
|
passwordStrength.config(width=90)
|
|
passwordStrength.config(bg="#ff781f")
|
|
elif not password:
|
|
passwordStrength.config(width=208)
|
|
passwordStrength.config(bg="#e5eaf0")
|
|
else:
|
|
passwordStrength.config(width=20)
|
|
passwordStrength.config(bg="#e3242b")
|
|
|
|
# Check if passwords match
|
|
def doPasswordsMatch():
|
|
global mode
|
|
if mode=="decrypt":
|
|
if passwordInput.get():
|
|
startBtn["state"] = "normal"
|
|
startBtn.config(cursor="hand2")
|
|
else:
|
|
startBtn["state"] = "disabled"
|
|
startBtn.config(cursor="")
|
|
else:
|
|
matches = passwordInput.get()==cPasswordInput.get()
|
|
|
|
# Passwords match
|
|
if passwordInput.get() and matches:
|
|
passwordMatchesString.set("✔️")
|
|
startBtn["state"] = "normal"
|
|
startBtn.config(cursor="hand2")
|
|
|
|
# Passwords don't match
|
|
elif passwordInput.get() and not matches:
|
|
passwordMatchesString.set("❌")
|
|
startBtn["state"] = "disabled"
|
|
startBtn.config(cursor="")
|
|
|
|
# Password is empty
|
|
elif not passwordInput.get():
|
|
passwordMatchesString.set("")
|
|
startBtn["state"] = "disabled"
|
|
startBtn.config(cursor="")
|
|
|
|
# Bind key releases to appropriate functions
|
|
passwordInput.bind("<KeyRelease>",lambda e:[showStrength(),doPasswordsMatch()])
|
|
cPasswordInput.bind("<KeyRelease>",lambda e:doPasswordsMatch())
|
|
|
|
# Show indicator of password strength
|
|
passwordStrength = tkinter.Frame(
|
|
tk,
|
|
height=2,
|
|
width=208
|
|
)
|
|
passwordStrength.config(bg="#e5eaf0")
|
|
passwordStrength.place(x=21,y=146)
|
|
|
|
# Label that indicates if passwords match
|
|
passwordMatchesString = tkinter.StringVar(tk)
|
|
passwordMatches = tkinter.ttk.Label(
|
|
tk,
|
|
textvariable=passwordMatchesString
|
|
)
|
|
passwordMatches.place(x=236,y=180)
|
|
|
|
# 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=210)
|
|
metadataLabel["state"] = "disabled"
|
|
|
|
# Frame so metadata box can fill width
|
|
metadataFrame = tkinter.Frame(
|
|
tk,
|
|
width=235,
|
|
height=99
|
|
)
|
|
metadataFrame.place(x=20,y=228)
|
|
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,
|
|
padx=5,
|
|
pady=5
|
|
)
|
|
metadataInput.config(font=("Consolas",10))
|
|
metadataInput.grid(row=0,column=0,sticky="nesw",padx=1,pady=1)
|
|
metadataInput.config(borderwidth=0)
|
|
metadataInput.config(bg="#fbfcfc")
|
|
metadataInput["state"] = "disabled"
|
|
|
|
# Tkinter's Text() boxes are ugly, so I (painfully) beautify it manually
|
|
metadataInput.bind(
|
|
"<FocusIn>",
|
|
lambda e:metadataBoxUI("in")
|
|
)
|
|
metadataInput.bind(
|
|
"<FocusOut>",
|
|
lambda e:metadataBoxUI("out")
|
|
)
|
|
def metadataBoxUI(what):
|
|
if what=="in":
|
|
if metadataInput.cget("bg")=="#ffffff":
|
|
metadataFrame.config(bg="#5294e2")
|
|
else:
|
|
metadataFrame.config(bg="#d8ddea")
|
|
|
|
# Another separator for aesthetics
|
|
tkinter.Frame(
|
|
tk,
|
|
bg="#dfe3ed",
|
|
width=1
|
|
).place(x=262,y=106,height=221)
|
|
|
|
# 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=17,y=337)
|
|
keepBtn["state"] = "disabled"
|
|
|
|
# Check box for securely erasing original file(s)
|
|
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=17,y=357)
|
|
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=17,y=377)
|
|
rsBtn["state"] = "disabled"
|
|
|
|
# "Reed-Solomon" which links to Wikipedia
|
|
rsHelpString = tkinter.StringVar(tk)
|
|
rsHelpString.set("(?)")
|
|
rsHelp = tkinter.ttk.Label(
|
|
tk,
|
|
textvariable=rsHelpString,
|
|
cursor="hand2",
|
|
font=("Helvetica",(7 if platform.system()=="Windows" else 9))
|
|
)
|
|
rsHelp.place(
|
|
x=(259 if platform.system()=="Windows" else 289),
|
|
y=(382 if platform.system()=="Windows" else 384)
|
|
)
|
|
rsHelpLink = "https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction"
|
|
rsHelp.bind("<Button-1>",lambda e:webbrowser.open(rsHelpLink))
|
|
|
|
# Frame so start and cancel button can fill width
|
|
startFrame = tkinter.Frame(
|
|
tk,
|
|
width=440,
|
|
height=(29 if platform.system()=="Windows" else 30)
|
|
)
|
|
startFrame.place(x=20,y=410)
|
|
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=wrapper,daemon=True).start()
|
|
)
|
|
startBtn.grid(row=0,column=0,stick="nesw")
|
|
startBtn["state"] = "disabled"
|
|
|
|
# Cancel encrypting/decrypting
|
|
def cancel():
|
|
global working
|
|
working = False
|
|
dummy.focus()
|
|
|
|
# Cancel button
|
|
cancelBtn = tkinter.ttk.Button(
|
|
startFrame,
|
|
text="Cancel",
|
|
command=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=(336 if platform.system()=="Windows" else 304),
|
|
mode="determinate"
|
|
)
|
|
progress.place(x=30,y=420)
|
|
|
|
# Lift start and cancel button in front of progress bar
|
|
startFrame.lift()
|
|
|
|
# Status label
|
|
statusString = tkinter.StringVar(tk)
|
|
statusString.set("Ready.")
|
|
status = tkinter.ttk.Label(
|
|
tk,
|
|
textvariable=statusString
|
|
)
|
|
status.place(x=20,y=448)
|
|
|
|
# Credits
|
|
hint = "Created by Evan Su. Click for details and source."
|
|
creditsString = tkinter.StringVar(tk)
|
|
creditsString.set(hint)
|
|
creditsLabel = tkinter.ttk.Label(
|
|
tk,
|
|
textvariable=creditsString,
|
|
cursor="hand2"
|
|
)
|
|
creditsLabel.place(x=20,y=468)
|
|
source = "https://github.com/HACKERALERT/Picocrypt"
|
|
creditsLabel.bind("<Button-1>",lambda e:webbrowser.open(source))
|
|
creditsLabel["state"] = "disabled"
|
|
|
|
# Version
|
|
versionString = tkinter.StringVar(tk)
|
|
versionString.set("v1.12.1")
|
|
version = tkinter.ttk.Label(
|
|
tk,
|
|
textvariable=versionString
|
|
)
|
|
version["state"] = "disabled"
|
|
version.place(x=(430 if platform.system()=="Windows" else 423),y=468)
|
|
|
|
# Drag files indicator window
|
|
prompt = tkinter.Frame(tk)
|
|
prompt.config(bg="#f5f6f7")
|
|
promptString = tkinter.StringVar(tk)
|
|
promptString.set("Drag and drop file(s) and folder(s) here.")
|
|
promptLabel = tkinter.ttk.Label(
|
|
prompt,
|
|
textvariable=promptString
|
|
)
|
|
promptLabel.place(x=135,y=311)
|
|
promptIconHor = tkinter.Frame(
|
|
prompt,
|
|
bg="#6f737d",
|
|
height=4
|
|
)
|
|
promptIconHor.place(x=208,y=261,width=64)
|
|
promptIconVer = tkinter.Frame(
|
|
prompt,
|
|
bg="#6f737d",
|
|
width=4
|
|
)
|
|
promptIconVer.place(x=238,y=231,height=64)
|
|
|
|
# Ask user to confirm overwrite
|
|
confirmOverwrite = tkinter.Frame(tk)
|
|
confirmOverwrite.config(bg="#f5f6f7")
|
|
confirmOverwriteString = tkinter.StringVar(tk)
|
|
confirmOverwriteString.set(strings[13])
|
|
confirmOverwriteLabel = tkinter.ttk.Label(
|
|
confirmOverwrite,
|
|
textvariable=confirmOverwriteString
|
|
)
|
|
confirmOverwriteLabel.place(x=91,y=200)
|
|
confirmOverwriteNo = tkinter.ttk.Button(
|
|
confirmOverwrite,
|
|
text="No",
|
|
cursor="hand2",
|
|
command=lambda:confirmOverwrite.pack_forget()
|
|
)
|
|
confirmOverwriteNo.place(x=147,y=245)
|
|
|
|
# Start encryption/decryption after user confirms
|
|
def overwriteConfirmed():
|
|
confirmOverwrite.pack_forget()
|
|
Thread(target=wrapper,daemon=True,args=(True,)).start()
|
|
|
|
confirmOverwriteYes = tkinter.ttk.Button(
|
|
confirmOverwrite,
|
|
text="Yes",
|
|
cursor="hand2",
|
|
command=overwriteConfirmed
|
|
)
|
|
confirmOverwriteYes.place(x=249,y=245)
|
|
|
|
# Files have been dragged
|
|
def filesDragged(draggedFiles):
|
|
global inputFile,rs128,onlyFiles,mode,onlyFolders,allFiles
|
|
resetUI()
|
|
status.config(cursor="")
|
|
status.bind("<Button-1>",lambda e:None)
|
|
|
|
# Use try to catch file errors
|
|
try:
|
|
# Create lists to track files dragged
|
|
onlyFiles = []
|
|
onlyFolders = []
|
|
allFiles = []
|
|
tmpName = ""
|
|
tmp = [i for i in draggedFiles]
|
|
res = []
|
|
within = False
|
|
|
|
"""
|
|
The next for loop parses data return by tkinterdnd2's file drop method.
|
|
When files and folders are dragged, the output (the 'draggedFile' parameter)
|
|
will contain the dropped files/folders and will look something like this:
|
|
|
|
A single file/folder: "C:\Foo\Bar.txt"
|
|
A single file/folder with a space in path: "{C:\Foo Bar\Lorem.txt}"
|
|
Multiple files/folders: "C:\Foo\Bar1.txt C:\Foo\Ba2.txt"
|
|
Multiple files/folders with spaces in paths:
|
|
- "C:\Foo\Bar1.txt {C:\Foo Bar\Lorem.txt}"
|
|
- "{C:\Foo Bar\Lorem.txt} C:\Foo\Bar1.txt"
|
|
- "{C:\Foo Bar\Lorem1.txt} {C:\Foo Bar\Lorem2.txt}"
|
|
"""
|
|
for i in tmp:
|
|
if i=="{":
|
|
within = True
|
|
elif i=="}":
|
|
within = False
|
|
res.append(tmpName)
|
|
tmpName = ""
|
|
else:
|
|
if i==" " and not within:
|
|
if tmpName:
|
|
res.append(tmpName)
|
|
tmpName = ""
|
|
else:
|
|
tmpName += i
|
|
if tmpName:
|
|
res.append(tmpName)
|
|
|
|
# Check each item dragged by user
|
|
for i in res:
|
|
# If it's a directory, recursively add all files
|
|
if isdir(i):
|
|
onlyFolders.append(i)
|
|
tmp = Path(i).rglob("*")
|
|
for p in tmp:
|
|
allFiles.append(abspath(p))
|
|
# Just a file, add it to 'onlyFiles'
|
|
else:
|
|
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")
|
|
fin.read(129)
|
|
metadataString.set("File metadata (read only):")
|
|
metadataInput["state"] = "normal"
|
|
metadataInput.delete("1.0",tkinter.END)
|
|
try:
|
|
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")
|
|
metadataInput.insert("1.0",metadata)
|
|
except:
|
|
# Metadata is corrupted, tell user
|
|
metadataInput.insert("1.0",strings[14])
|
|
metadataInput["state"] = "disabled"
|
|
fin.close()
|
|
|
|
# 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()
|
|
startBtn["state"] = "disabled"
|
|
startBtn.config(cursor="")
|
|
else:
|
|
mode = "encrypt"
|
|
|
|
# Update UI
|
|
setEncryptionUI()
|
|
startBtn["state"] = "disabled"
|
|
startBtn.config(cursor="")
|
|
|
|
# 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("\\","/")
|
|
inputFile = tmp
|
|
outputInput.insert(0,tmp)
|
|
suffix = " (will encrypt)"
|
|
|
|
nFiles = len(onlyFiles)
|
|
nFolders = len(onlyFolders)
|
|
|
|
# Show selected file(s) and folder(s)
|
|
if (allFiles or onlyFiles) and not onlyFolders:
|
|
inputString.set(f"{nFiles} files selected (will encrypt).")
|
|
elif onlyFolders and not onlyFiles:
|
|
inputString.set(f"{nFolders} folder{'s' if nFolders!=1 else ''} selected (will encrypt).")
|
|
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)
|
|
|
|
prompt.pack_forget()
|
|
statusString.set("Ready.")
|
|
|
|
# UTF-8 decode error
|
|
except UnicodeDecodeError:
|
|
statusString.set(strings[20])
|
|
|
|
# Nothing happened
|
|
except:
|
|
pass
|
|
|
|
# Bind drag and drop to window
|
|
def onDrop(e):
|
|
global working
|
|
if not working:
|
|
filesDragged(e.data)
|
|
clearInput["state"] = "normal"
|
|
clearInput.config(cursor="hand2")
|
|
def onDropEnter(e):
|
|
prompt.pack(expand=1,fill=tkinter.BOTH)
|
|
prompt.lift()
|
|
def onDropLeave(e):
|
|
prompt.pack_forget()
|
|
|
|
# Bind functions to window
|
|
tk.drop_target_register(DND_FILES)
|
|
tk.dnd_bind("<<Drop>>",onDrop)
|
|
tk.dnd_bind("<<DropEnter>>",onDropEnter)
|
|
tk.dnd_bind("<<DropLeave>>",onDropLeave)
|
|
|
|
# Main encryption/decryption function
|
|
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")
|
|
cancelBtn["state"] = "normal"
|
|
cancelBtn.config(cursor="hand2")
|
|
|
|
# Decide if encrypting or decrypting
|
|
if mode=="encrypt":
|
|
outputFile = outputInput.get()+".pcv"
|
|
else:
|
|
outputFile = outputInput.get()
|
|
|
|
# Set progress bar indeterminate
|
|
progress.config(mode="indeterminate")
|
|
progress.start(15)
|
|
|
|
# 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)
|
|
zf = ZipFile(zfName,"w")
|
|
for i in allFiles:
|
|
zf.write(i,i[zfOffset:])
|
|
for i in onlyFiles:
|
|
zf.write(i,pathSplit(i)[1])
|
|
zf.close()
|
|
inputFile = zfName
|
|
outputFile = zfName+".pcv"
|
|
outputPath = dirname(outputFile)
|
|
|
|
# Open files
|
|
try:
|
|
fin = open(inputFile,"rb")
|
|
except:
|
|
setEncryptionUI()
|
|
statusString.set(strings[16])
|
|
return
|
|
|
|
# If encrypting, generate values for encryption
|
|
if mode=="encrypt":
|
|
salt = urandom(16) # Argon2 salt
|
|
nonce = urandom(24) # XChaCha20 nonce
|
|
fout = open(outputFile,"wb+")
|
|
|
|
# Indicate Reed-Solomon with "+"
|
|
if reedsolo:
|
|
fout.write(rs128.encode(b"+"))
|
|
else:
|
|
fout.write(rs128.encode(b"-"))
|
|
|
|
# Encode metadata and length of metadata
|
|
metadata = rs128.encode(metadata)
|
|
tmp = len(metadata)
|
|
tmp = f"{tmp:+<10}"
|
|
tmp = rs128.encode(tmp.encode("utf-8"))
|
|
|
|
# Write to file
|
|
fout.write(tmp)
|
|
fout.write(metadata)
|
|
fout.write(rs128.encode(salt)) # Argon2 salt
|
|
fout.write(rs128.encode(nonce)) # XChaCha20 nonce
|
|
fout.write(b"0"*192) # Hash of key
|
|
fout.write(b"0"*144) # Poly1305 MAC
|
|
fout.write(b"0"*160) # BLAKE3 CRC
|
|
|
|
# If decrypting, read values from file
|
|
else:
|
|
tmp = fin.read(129)
|
|
try:
|
|
if bytes(rs128.decode(tmp)[0])==b"+":
|
|
reedsolo = True
|
|
else:
|
|
reedsolo = False
|
|
except:
|
|
setDecryptionUI()
|
|
statusString.set(strings[21])
|
|
return
|
|
|
|
metadataLength = fin.read(138)
|
|
metadataLength = bytes(rs128.decode(metadataLength)[0])
|
|
metadataLength = metadataLength.replace(b"+",b"")
|
|
fin.read(int(metadataLength.decode("utf-8")))
|
|
|
|
# Read values
|
|
salt = fin.read(144)
|
|
nonce = fin.read(152)
|
|
keycs = fin.read(192)
|
|
maccs = fin.read(144)
|
|
crccs = fin.read(160)
|
|
|
|
# Try to decode each value, increase Reed-Solomon errors fixed if needed
|
|
try:
|
|
salt,_,fixed = rs128.decode(salt)
|
|
salt = bytes(salt)
|
|
reedsoloFixed += len(fixed)
|
|
except:
|
|
headerBroken = True
|
|
salt = salt[:16]
|
|
try:
|
|
nonce,_,fixed = rs128.decode(nonce)
|
|
nonce = bytes(nonce)
|
|
reedsoloFixed += len(fixed)
|
|
except:
|
|
headerBroken = True
|
|
nonce = nonce[:24]
|
|
try:
|
|
keycs,_,fixed = rs128.decode(keycs)
|
|
keycs = bytes(keycs)
|
|
reedsoloFixed += len(fixed)
|
|
except:
|
|
headerBroken = True
|
|
keycs = keycs[:64]
|
|
try:
|
|
maccs,_,fixed = rs128.decode(maccs)
|
|
maccs = bytes(maccs)
|
|
reedsoloFixed += len(fixed)
|
|
except:
|
|
headerBroken = True
|
|
maccs = maccs[:16]
|
|
try:
|
|
crccs,_,fixed = rs128.decode(crccs)
|
|
crccs = bytes(crccs)
|
|
reedsoloFixed += len(fixed)
|
|
except:
|
|
headerBroken = True
|
|
crccs = crccs[:32]
|
|
|
|
# If the header is broken...
|
|
if headerBroken:
|
|
# Stop if user chose not to keep broken output
|
|
if not shouldKeep:
|
|
statusString.set(strings[4])
|
|
fin.close()
|
|
try:
|
|
remove(outputFile)
|
|
except:
|
|
pass
|
|
setDecryptionUI()
|
|
return
|
|
else:
|
|
kept = "badlyCorrupted"
|
|
|
|
statusString.set(strings[9])
|
|
|
|
# Generate Argon2d key from master password
|
|
key = hash_secret_raw(
|
|
password,
|
|
salt,
|
|
time_cost=8,
|
|
memory_cost=2**20,
|
|
parallelism=8,
|
|
hash_len=32,
|
|
type=argonType.D
|
|
)
|
|
|
|
# Stop the indeterminate progress bar and set determinate
|
|
progress.stop()
|
|
progress.config(mode="determinate")
|
|
progress["value"] = 0
|
|
|
|
# Hash of the derived Argon2 key
|
|
check = SHA3_512.new(data=key).digest()
|
|
|
|
# Check if password is correct
|
|
if mode=="decrypt":
|
|
if not compare_digest(check,keycs):
|
|
# If header isn't broken...
|
|
if not headerBroken:
|
|
# Tell user password is incorrect
|
|
statusString.set(strings[2])
|
|
fin.close()
|
|
setDecryptionUI()
|
|
return
|
|
fout = open(outputFile,"wb+")
|
|
|
|
crc = blake3() # Blake3 CRC
|
|
cipher = ChaCha20_Poly1305.new(key=key,nonce=nonce) # XChaCha20
|
|
|
|
# Variables for calculating speeds, etc.
|
|
done = 0
|
|
total = getsize(inputFile)
|
|
startTime = datetime.now()
|
|
previousTime = datetime.now()
|
|
|
|
# Update progress bar, etc. in another thread
|
|
Thread(target=updateStats,daemon=True,args=(total,)).start()
|
|
|
|
# Start the encryption/decryption process
|
|
while True:
|
|
# Check if cancel button pressed
|
|
if not working:
|
|
fin.close()
|
|
fout.close()
|
|
remove(outputFile)
|
|
if mode=="encrypt":
|
|
setEncryptionUI()
|
|
else:
|
|
setDecryptionUI()
|
|
statusString.set("Operation canceled by user.")
|
|
dummy.focus()
|
|
return
|
|
|
|
# Read from file, read extra if Reed-Solomon was enabled
|
|
if mode=="decrypt" and reedsolo:
|
|
piece = fin.read(1104905)
|
|
else:
|
|
piece = fin.read(2**20)
|
|
|
|
# End of file
|
|
if not piece:
|
|
break
|
|
|
|
# Encrypt, etc.
|
|
if mode=="encrypt":
|
|
data = cipher.encrypt(piece)
|
|
crc.update(data)
|
|
if reedsolo:
|
|
data = bytes(rs13.encode(data))
|
|
|
|
# Decrypt, etc.
|
|
else:
|
|
if reedsolo:
|
|
try:
|
|
data,_,fixed = rs13.decode(piece)
|
|
except ReedSolomonError:
|
|
# File is really corrupted
|
|
if not reedsoloErrors and not shouldKeep:
|
|
stopUpdating = True
|
|
statusString.set(strings[4])
|
|
fin.close()
|
|
fout.close()
|
|
remove(outputFile)
|
|
setDecryptionUI()
|
|
return
|
|
|
|
# Attempt to recover badly corrupted data
|
|
kept = "badlyCorrupted"
|
|
data = b""
|
|
piece = piece[:-13]
|
|
counter = 0
|
|
while True:
|
|
# Basically just strip off the Reed-Solomon bytes
|
|
if counter<1104905:
|
|
data += piece[counter:counter+242]
|
|
counter += 255 # 242 bytes + 13 Reed-Solomon
|
|
else:
|
|
break
|
|
fixed = bytearray()
|
|
reedsoloErrors += 1
|
|
|
|
reedsoloFixed += len(fixed)
|
|
crc.update(data)
|
|
data = cipher.decrypt(data)
|
|
|
|
else:
|
|
crc.update(piece)
|
|
data = cipher.decrypt(piece)
|
|
|
|
# Write the data, increase the amount done
|
|
fout.write(data)
|
|
done += 1104905 if (mode=="decrypt" and reedsolo) else 2**20
|
|
|
|
# Stop UI updater from overwriting potential messages
|
|
stopUpdating = True
|
|
|
|
# Encryption is done, write appropriate values to file
|
|
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()))
|
|
|
|
# Decryption is done, check for integrity and authenticity
|
|
else:
|
|
# File is corrupted
|
|
if not compare_digest(crccs,crc.digest()):
|
|
statusString.set(strings[3])
|
|
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(strings[5])
|
|
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()
|
|
|
|
# Securely wipe files as necessary
|
|
if shouldErase:
|
|
if onlyFolders:
|
|
for i in onlyFolders:
|
|
if not working:
|
|
break
|
|
secureWipe(i)
|
|
if onlyFiles:
|
|
for i in range(len(onlyFiles)):
|
|
if not working:
|
|
break
|
|
statusString.set(strings[12]+f" ({i}/{len(onlyFiles)})")
|
|
progress["value"] = i/len(onlyFiles)
|
|
secureWipe(onlyFiles[i])
|
|
secureWipe(inputFile)
|
|
|
|
# Secure wipe not enabled
|
|
else:
|
|
# Remove temporary zip file if created
|
|
if allFiles or onlyFiles:
|
|
remove(inputFile)
|
|
|
|
# Prevent Unicode nonsense on MacOS
|
|
arrow = "" if platform.system()=="Darwin" else "🡪"
|
|
|
|
# If user hit 'Cancel' during secure wiping...
|
|
if not working:
|
|
fin.close()
|
|
fout.close()
|
|
resetUI()
|
|
statusString.set(f"Secure wipe canceled, but encryption was successful. (Output {arrow})")
|
|
else:
|
|
# Show appropriate notice if file corrupted or modified
|
|
|
|
if not kept:
|
|
statusString.set(f"Completed. (Click here to show output {arrow})")
|
|
# Show Reed-Solomon stats if it fixed corrupted bytes
|
|
if mode=="decrypt" and reedsoloFixed:
|
|
tmp = "s" if reedsoloFixed!=1 else ""
|
|
statusString.set(
|
|
f"Completed with {reedsoloFixed} byte{tmp}"+
|
|
f" fixed. (Click here to show output {arrow})"
|
|
)
|
|
else:
|
|
if kept=="modified":
|
|
statusString.set(strings[7])
|
|
elif kept=="corrupted":
|
|
statusString.set(strings[6])
|
|
else:
|
|
statusString.set(strings[8])
|
|
|
|
status.config(cursor="hand2")
|
|
|
|
# A little hack to prevent reference nonsense
|
|
output = "".join([i for i in outputFile])
|
|
|
|
# Bind the output file to the status label
|
|
if platform.system()=="Windows":
|
|
status.bind("<Button-1>",lambda e:showOutput(output.replace("/","\\")))
|
|
else:
|
|
status.bind("<Button-1>",lambda e:showOutput(output))
|
|
|
|
# Reset variables and UI states
|
|
resetUI()
|
|
inputFile = ""
|
|
outputFile = ""
|
|
allFiles = []
|
|
onlyFolders = []
|
|
onlyFiles = []
|
|
working = False
|
|
|
|
# Wraps the work() function and prevents overwrites
|
|
def wrapper(yes=False):
|
|
global working,mode,outputFile
|
|
if mode=="encrypt":
|
|
outputFile = outputInput.get()+".pcv"
|
|
else:
|
|
outputFile = outputInput.get()
|
|
try:
|
|
getsize(outputFile)
|
|
if not yes:
|
|
confirmOverwrite.pack(expand=1,fill=tkinter.BOTH)
|
|
confirmOverwrite.lift()
|
|
return
|
|
except:
|
|
pass
|
|
try:
|
|
work()
|
|
except:
|
|
working = False
|
|
if mode=="encrypt":
|
|
setEncryptionUI()
|
|
else:
|
|
setDecryptionUI()
|
|
statusString.set(strings[17])
|
|
finally:
|
|
dummy.focus()
|
|
sys.exit(0)
|
|
|
|
# Update visuals with stats
|
|
def updateStats(total):
|
|
global startTime,previousTime,done,stopUpdating,reedsolo,reedsoloFixed,reedsoloErrors,working
|
|
while True:
|
|
validStatus = (
|
|
statusString.get().startswith("Working") or statusString.get().startswith("Deriving")
|
|
)
|
|
# Make sure to stop updating if needed
|
|
if not stopUpdating and validStatus and working:
|
|
# Some basic math to 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 = max(round((total-done)/(speed*10**6)),0)
|
|
eta = str(timedelta(seconds=min(eta,86399))).zfill(8)
|
|
|
|
info = f"Working... {min(percent,100):.0f}% at {speed:.2f} MB/s (ETA: {eta})"
|
|
|
|
# Show the number of fixed and unrecoverable bytes
|
|
if reedsolo and mode=="decrypt" and reedsoloFixed:
|
|
tmp = "s" if reedsoloFixed!=1 else ""
|
|
info += f", fixed {reedsoloFixed} byte{tmp}"
|
|
|
|
if reedsolo and mode=="decrypt" and reedsoloErrors:
|
|
info += f", {reedsoloErrors} MB unrecoverable"
|
|
|
|
statusString.set(info)
|
|
sleep(0.05)
|
|
else:
|
|
sys.exit(0)
|
|
break
|
|
|
|
# Securely wipe file(s) via system internals
|
|
def secureWipe(fin):
|
|
global working
|
|
statusString.set(strings[12])
|
|
progress["value"] = 100
|
|
# Check platform, erase accordingly
|
|
if platform.system()=="Windows":
|
|
# Recursively delete folders
|
|
if isdir(fin):
|
|
paths = []
|
|
for i in Path(fin).rglob("*"):
|
|
if dirname(i) not in paths:
|
|
paths.append(dirname(i))
|
|
for i in range(len(paths)):
|
|
if not working:
|
|
return
|
|
statusString.set(strings[12]+f" ({i}/{len(paths)})")
|
|
progress["value"] = 100*i/len(paths)
|
|
system(f'cd "{paths[i]}" && "{rootDir}/sdelete64.exe" * -p 4 -nobanner')
|
|
system(f'cd "{rootDir}"')
|
|
rmtree(fin)
|
|
else:
|
|
system(f'sdelete64.exe "{fin}" -p 4 -nobanner')
|
|
|
|
# MacOS
|
|
elif platform.system()=="Darwin":
|
|
system(f'rm -rfP "{fin}"')
|
|
|
|
# Linux
|
|
else:
|
|
system(f'shred -uz "{fin}" -n 4')
|
|
|
|
# Show output file in user's file manager
|
|
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)}"')
|
|
|
|
# Reset UI to state where no files are selected
|
|
def resetUI():
|
|
global working
|
|
working = False
|
|
inputString.set(strings[18])
|
|
inputLabel["state"] = "normal"
|
|
clearInput["state"] = "disabled"
|
|
clearInput.config(cursor="")
|
|
outputLabel["state"] = "disabled"
|
|
saveAsBtn.config(cursor="")
|
|
saveAsBtn["state"] = "disabled"
|
|
orLabel.place(x=356,y=71)
|
|
orLabel["state"] = "disabled"
|
|
outputFrame.config(width=320)
|
|
outputInput["state"] = "normal"
|
|
outputInput.delete(0,"end")
|
|
outputInput["state"] = "disabled"
|
|
pcvLabel["state"] = "normal"
|
|
passwordLabel["state"] = "disabled"
|
|
passwordInput["state"] = "normal"
|
|
passwordInput.delete(0,"end")
|
|
passwordInput["state"] = "disabled"
|
|
passwordInput.config(show="•")
|
|
passwordShow["state"] = "disabled"
|
|
cPasswordString.set("Confirm password:")
|
|
cPasswordLabel["state"] = "disabled"
|
|
cPasswordInput["state"] = "normal"
|
|
cPasswordInput.delete(0,"end")
|
|
cPasswordInput["state"] = "disabled"
|
|
cPasswordInput.config(show="•")
|
|
passwordStrength.config(width=208)
|
|
passwordStrength.config(bg="#e5eaf0")
|
|
passwordMatchesString.set("")
|
|
passwordMatches["state"] = "normal"
|
|
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"
|
|
erase.set(0)
|
|
eraseBtn["state"] = "disabled"
|
|
rs.set(0)
|
|
rsBtn["state"] = "disabled"
|
|
startFrame.lift()
|
|
startBtn["state"] = "disabled"
|
|
startBtn.config(cursor="")
|
|
cancelBtn["state"] = "disabled"
|
|
cancelBtn.config(cursor="")
|
|
progress.stop()
|
|
progress.config(mode="determinate")
|
|
progress["value"] = 0
|
|
dummy.focus()
|
|
|
|
# Set UI to encryption state
|
|
def setEncryptionUI():
|
|
global working
|
|
working = False
|
|
clearInput["state"] = "normal"
|
|
clearInput.config(cursor="hand2")
|
|
saveAsBtn.config(cursor="hand2")
|
|
saveAsBtn["state"] = "normal"
|
|
orLabel.place(x=353,y=71)
|
|
orLabel["state"] = "normal"
|
|
outputLabel["state"] = "normal"
|
|
outputInput["state"] = "normal"
|
|
outputFrame.config(width=290)
|
|
pcvLabel["state"] = "normal"
|
|
passwordLabel["state"] = "normal"
|
|
passwordInput["state"] = "normal"
|
|
passwordShow["state"] = "normal"
|
|
showStrength()
|
|
cPasswordLabel["state"] = "normal"
|
|
cPasswordString.set("Confirm password:")
|
|
cPasswordInput["state"] = "normal"
|
|
passwordMatches["state"] = "normal"
|
|
metadataFrame.config(bg="#cfd6e6")
|
|
metadataInput.config(bg="#ffffff")
|
|
metadataInput.config(fg="#000000")
|
|
metadataLabel["state"] = "normal"
|
|
metadataInput["state"] = "normal"
|
|
eraseBtn["state"] = "normal"
|
|
rsBtn["state"] = "normal"
|
|
startFrame.lift()
|
|
startBtn["state"] = "normal"
|
|
startBtn.config(cursor="hand2")
|
|
cancelBtn["state"] = "disabled"
|
|
cancelBtn.config(cursor="")
|
|
progress.stop()
|
|
progress.config(mode="determinate")
|
|
progress["value"] = 0
|
|
|
|
# Set UI to decryption state
|
|
def setDecryptionUI():
|
|
global working
|
|
working = False
|
|
clearInput["state"] = "normal"
|
|
clearInput.config(cursor="hand2")
|
|
saveAsBtn.config(cursor="hand2")
|
|
saveAsBtn["state"] = "normal"
|
|
orLabel.place(x=356,y=71)
|
|
orLabel["state"] = "normal"
|
|
outputLabel["state"] = "normal"
|
|
outputInput["state"] = "normal"
|
|
outputFrame.config(width=320)
|
|
passwordLabel["state"] = "normal"
|
|
passwordInput["state"] = "normal"
|
|
passwordShow["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"
|
|
startFrame.lift()
|
|
startBtn["state"] = "normal"
|
|
startBtn.config(cursor="hand2")
|
|
cancelBtn["state"] = "disabled"
|
|
cancelBtn.config(cursor="")
|
|
progress.stop()
|
|
progress.config(mode="determinate")
|
|
progress["value"] = 0
|
|
|
|
# Disable all inputs while encrypting/decrypting
|
|
def disableAllInputs():
|
|
clearInput["state"] = "disabled"
|
|
clearInput.config(cursor="")
|
|
saveAsBtn.config(cursor="")
|
|
saveAsBtn["state"] = "disabled"
|
|
orLabel["state"] = "disabled"
|
|
outputInput["state"] = "disabled"
|
|
pcvLabel["state"] = "disabled"
|
|
passwordInput["state"] = "disabled"
|
|
passwordInput.config(show="•")
|
|
passwordShow["state"] = "disabled"
|
|
passwordStrength.config(width=208)
|
|
passwordStrength.config(bg="#e5eaf0")
|
|
cPasswordInput["state"] = "disabled"
|
|
cPasswordInput.config(show="•")
|
|
cPasswordString.set("Confirm password:")
|
|
passwordMatches["state"] = "disabled"
|
|
metadataFrame.config(bg="#e5eaf0")
|
|
metadataInput.config(bg="#fbfcfc")
|
|
metadataInput.config(fg="#666666")
|
|
metadataInput["state"] = "disabled"
|
|
progress.lift()
|
|
startBtn["state"] = "disabled"
|
|
startBtn.config(cursor="")
|
|
eraseBtn["state"] = "disabled"
|
|
keepBtn["state"] = "disabled"
|
|
rsBtn["state"] = "disabled"
|
|
|
|
# Allow window to close if not encrypting/decrypting
|
|
def onClose():
|
|
global working
|
|
if not working:
|
|
tk.destroy()
|
|
|
|
# Generate Reed-Solomon codecs
|
|
def prepare():
|
|
global rs13,rs128
|
|
rs13 = RSCodec(13)
|
|
rs128 = RSCodec(128)
|
|
|
|
# Accept sdelete64's EULA
|
|
if platform.system()=="Windows":
|
|
system("sdelete64.exe /accepteula")
|
|
sys.exit(0)
|
|
|
|
# Prepare Reed-Solomon codecs
|
|
Thread(target=prepare,daemon=True).start()
|
|
|
|
# Bind tkinter close event
|
|
tk.protocol("WM_DELETE_WINDOW",onClose)
|
|
|
|
# Start tkinter
|
|
tk.mainloop()
|
|
sys.exit(0)
|