Update Picocrypt.py

This commit is contained in:
Evan Su 2021-03-23 18:55:57 -04:00 committed by GitHub
parent d101b779f5
commit a2ab95ec53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 417 additions and 232 deletions

View File

@ -1,34 +1,17 @@
#!/usr/bin/env python3
"""
Dependencies: argon2-cffi, pycryptodome, reedsolo
Picocrypt v1.11 (Beta)
Dependencies: argon2-cffi, pycryptodome, reedsolo, tkinterdnd2
Copyright (c) Evan Su (https://evansu.cc)
Released under a GNU GPL v3 License
https://github.com/HACKERALERT/Picocrypt
~ In cryptography we trust ~
"""
# Test if libraries are installed
try:
from argon2.low_level import hash_secret_raw
from Crypto.Cipher import ChaCha20_Poly1305
try:
from creedsolo import ReedSolomonError
except:
from reedsolo import ReedSolomonError
except:
# Libraries missing, install them
from os import system
try:
# Debian/Ubuntu based
system("sudo apt-get install python3-tk")
except:
# Fedora
system("sudo dnf install python3-tkinter")
system("python3 -m pip install argon2-cffi --no-cache-dir")
system("python3 -m pip install pycryptodome --no-cache-dir")
system("python3 -m pip install reedsolo --no-cache-dir")
# Imports
from tkinter import filedialog,messagebox
from threading import Thread
@ -37,17 +20,22 @@ from argon2.low_level import hash_secret_raw,Type
from Crypto.Cipher import ChaCha20_Poly1305
from Crypto.Hash import SHA3_512 as sha3_512
from secrets import compare_digest
from os import urandom,fsync,remove
from os.path import getsize,expanduser
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 pathlib import Path
from shutil import rmtree
import sys
import tkinter
import tkinter.ttk
import tkinter.scrolledtext
import webbrowser
try:
import platform
from creedsolo import RSCodec,ReedSolomonError
except:
from reedsolo import RSCodec,ReedSolomonError
# Tk/Tcl is a little barbaric, so I'm disabling
# high DPI so it doesn't scale bad and look horrible
@ -57,16 +45,22 @@ try:
except:
pass
# Global variables and notices
# Global variables and strings
rootDir = dirname(realpath(__file__))
inputFile = ""
outputFile = ""
outputPath = ""
password = ""
ad = ""
kept = False
working = False
gMode = None
headerRsc = None
headerRsc = 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."
@ -77,16 +71,21 @@ kVeryCorruptedNotice = "The input file is badly corrupted, but the output has be
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."
# Create root Tk
tk = tkinter.Tk()
tk.geometry("480x480")
tk = TkinterDnD.Tk()
tk.geometry("480x470")
tk.title("Picocrypt")
tk.configure(background="#f5f6f7")
if platform.system()=="Darwin":
tk.configure(background="#edeced")
else:
tk.configure(background="#ffffff")
tk.resizable(0,0)
# Try setting window icon if included with Picocrypt
@ -98,29 +97,85 @@ except:
# Some styling
s = tkinter.ttk.Style()
s.configure("TCheckbutton",background="#f5f6f7")
s.configure("TCheckbutton",background="#ffffff")
# Event when user selects an input file
def inputSelected():
global inputFile,working,headerRsc
# Event when user drags file(s) and folder(s) into window
def inputSelected(draggedFile):
global inputFile,working,headerRsc,allFiles,draggedFolderPaths,files
resetUI()
dummy.focus()
status.config(cursor="")
status.bind("<Button-1>",lambda e:None)
# Try to handle when select file is cancelled
# Use try to handle errors
try:
# Ask for input file
# Create list of input files
allFiles = []
files = []
draggedFolderPaths = []
suffix = ""
tmp = filedialog.askopenfilename(
initialdir=expanduser("~")
)
if len(tmp)==0:
# Exception will be caught by except below
raise Exception("No file selected.")
inputFile = tmp
tmp = [i for i in draggedFile]
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)
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)
allFiles = []
files = []
# Check each thing dragged by user
for i in res:
# If there is a directory, recursively add all files to 'allFiles'
if isdir(i):
# Record the directory for secure wipe (if necessary)
draggedFolderPaths.append(i)
tmp = Path(i).rglob("*")
for p in tmp:
allFiles.append(abspath(p))
# Just a file, add it to files
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 = []
else:
inputFile = ""
# Decide if encrypting or decrypting
if ".pcv" in inputFile.split("/")[-1]:
if inputFile.endswith(".pcv"):
suffix = " (will decrypt)"
fin = open(inputFile,"r+b")
fin = open(inputFile,"rb")
# Read file metadata (a little complex)
tmp = fin.read(139)
@ -148,6 +203,8 @@ def inputSelected():
adArea.delete("1.0",tkinter.END)
adArea.insert("1.0",ad)
adArea["state"] = "disabled"
# Update UI
adLabelString.set("File metadata (read only):")
keepBtn["state"] = "normal"
eraseBtn["state"] = "disabled"
@ -155,6 +212,7 @@ def inputSelected():
cpasswordInput["state"] = "normal"
cpasswordInput.delete(0,"end")
cpasswordInput["state"] = "disabled"
cpasswordString.set("Confirm password (N/A):")
else:
# Update the UI
eraseBtn["state"] = "normal"
@ -166,13 +224,33 @@ def inputSelected():
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)
# Show selected file(s) and folder(s)
if (allFiles or files) and not draggedFolderPaths:
inputString.set(f"{nFiles} files selected (will encrypt).")
elif draggedFolderPaths and not files:
inputString.set(f"{nFolders} folder{'s' if nFolders!=1 else ''} selected (will encrypt).")
elif draggedFolderPaths and (allFiles or files):
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.
inputString.set(inputFile.split("/")[-1]+suffix)
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
@ -180,32 +258,54 @@ def inputSelected():
statusString.set(corruptedNotice)
progress["value"] = 100
# No file selected, do nothing
# No file(s) selected, do nothing
except:
pass
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
# Button to select input file
selectFileInput = tkinter.ttk.Button(
tk,
text="Select file",
command=inputSelected,
)
selectFileInput.place(x=19,y=20)
# Clears the selected files
def clearInputs():
dummy.focus()
resetUI()
# Allow drag and drop
def onDrop(e):
global working
if not working:
inputSelected(e.data)
tk.drop_target_register(DND_FILES)
tk.dnd_bind("<<Drop>>",onDrop)
# Label that displays selected input file
inputString = tkinter.StringVar(tk)
inputString.set("Please select a file.")
inputString.set("Drag and drop file(s) and folder(s) into this window.")
selectedInput = tkinter.ttk.Label(
tk,
textvariable=inputString
)
selectedInput.config(background="#f5f6f7")
selectedInput.place(x=104,y=23)
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)
@ -214,16 +314,17 @@ passwordLabel = tkinter.ttk.Label(
tk,
textvariable=passwordString
)
passwordLabel.place(x=17,y=56)
passwordLabel.config(background="#f5f6f7")
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=440,
width=(445 if platform.system()=="Darwin" else 440),
height=22
)
passwordFrame.place(x=20,y=76)
passwordFrame.place(x=(17 if platform.system()=="Darwin" else 20),y=66)
passwordFrame.columnconfigure(0,weight=10)
passwordFrame.grid_propagate(False)
# Password input box
@ -240,16 +341,17 @@ cpasswordLabel = tkinter.ttk.Label(
tk,
textvariable=cpasswordString
)
cpasswordLabel.place(x=17,y=106)
cpasswordLabel.config(background="#f5f6f7")
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=440,
width=(445 if platform.system()=="Darwin" else 440),
height=22
)
cpasswordFrame.place(x=20,y=126)
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
@ -262,7 +364,9 @@ cpasswordInput["state"] = "disabled"
# Start the encryption/decryption process
def start():
global inputFile,outputFile,password,ad,kept,working,gMode,headerRsc
global inputFile,outputFile,password,ad,kept
global working,gMode,headerRsc,allFiles,files
global dragFolderPath
dummy.focus()
reedsolo = False
chunkSize = 2**20
@ -277,7 +381,7 @@ def start():
mode = "decrypt"
gMode = "decrypt"
# Check if Reed-Solomon was enabled by checking for "+"
test = open(inputFile,"rb+")
test = open(inputFile,"rb")
decider = test.read(1).decode("utf-8")
test.close()
if decider=="+":
@ -288,7 +392,7 @@ def start():
# Check if file already exists (getsize() throws error if file not found)
try:
getsize(outputFile)
force = messagebox.askyesno("Warning",overwriteNotice)
force = messagebox.askyesno("Confirmation",overwriteNotice)
dummy.focus()
if force!=1:
return
@ -296,26 +400,11 @@ def start():
pass
# Disable inputs and buttons while encrypting/decrypting
selectFileInput["state"] = "disabled"
passwordInput["state"] = "disabled"
cpasswordInput["state"] = "disabled"
adArea["state"] = "disabled"
startBtn["state"] = "disabled"
eraseBtn["state"] = "disabled"
keepBtn["state"] = "disabled"
rsBtn["state"] = "disabled"
disableAllInputs()
# Make sure passwords match
if passwordInput.get()!=cpasswordInput.get() and mode=="encrypt":
selectFileInput["state"] = "normal"
passwordInput["state"] = "normal"
cpasswordInput["state"] = "normal"
adArea["state"] = "normal"
startBtn["state"] = "normal"
eraseBtn["state"] = "normal"
rsBtn["state"] = "normal"
working = False
progress["value"] = 100
resetEncryptionUI()
statusString.set("Passwords don't match.")
return
@ -329,6 +418,27 @@ def start():
# 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()
else:
zfPath = Path(dirname(allFiles[0])).parent.absolute()
zfOffset = len(str(zfPath))
zfName = pathJoin(zfPath,tmp+".zip")
zf = ZipFile(zfName,"w")
for i in allFiles:
zf.write(i,i[zfOffset:])
for i in files:
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
@ -340,7 +450,13 @@ def start():
wipe = erase.get()==1
# Open files
fin = open(inputFile,"rb+")
try:
fin = open(inputFile,"rb")
except:
resetEncryptionUI()
statusString.set("Folder is empty.")
return
if reedsolo and mode=="decrypt":
# Move pointer one forward
fin.read(1)
@ -425,15 +541,7 @@ def start():
fout.close()
remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal"
passwordInput["state"] = "normal"
adArea["state"] = "normal"
startBtn["state"] = "normal"
keepBtn["state"] = "normal"
working = False
progress.stop()
progress.config(mode="determinate")
progress["value"] = 100
resetDecryptionUI()
return
else:
kept = "badlyCorrupted"
@ -472,14 +580,7 @@ def start():
fout.close()
remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal"
passwordInput["state"] = "normal"
adArea["state"] = "normal"
startBtn["state"] = "normal"
keepBtn["state"] = "normal"
working = False
progress["value"] = 100
del key
resetDecryptionUI()
return
# Create XChaCha20-Poly1305 object
@ -492,9 +593,6 @@ def start():
total = getsize(inputFile)
# If secure wipe enabled, create a wiper object
if wipe:
wiper = open(inputFile,"r+b")
wiper.seek(0)
# Keep track of time because it flies...
startTime = datetime.now()
@ -507,11 +605,7 @@ def start():
piece = fin.read(1104905)
else:
piece = fin.read(chunkSize)
if wipe:
# If securely wipe, write random trash
# to original file after reading it
trash = urandom(len(piece))
wiper.write(trash)
# If EOF
if not piece:
if mode=="encrypt":
@ -540,12 +634,7 @@ def start():
if keep.get()!=1:
remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal"
passwordInput["state"] = "normal"
adArea["state"] = "normal"
startBtn["state"] = "normal"
keepBtn["state"] = "normal"
working = False
resetDecryptionUI()
del fin,fout,cipher,key
return
else:
@ -566,12 +655,7 @@ def start():
if keep.get()!=1:
remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal"
passwordInput["state"] = "normal"
adArea["state"] = "normal"
startBtn["state"] = "normal"
keepBtn["state"] = "normal"
working = False
resetDecryptionUI()
del fin,fout,cipher,key
return
else:
@ -605,13 +689,7 @@ def start():
fout.close()
remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal"
passwordInput["state"] = "normal"
adArea["state"] = "normal"
startBtn["state"] = "normal"
keepBtn["state"] = "normal"
working = False
progress["value"] = 100
resetDecryptionUI()
del fin,fout,cipher,key
return
else:
@ -639,60 +717,76 @@ def start():
data = cipher.decrypt(piece)
# Calculate speed, ETA, etc.
first = False
elapsed = (datetime.now()-previousTime).total_seconds() or 0.0001
sinceStart = (datetime.now()-startTime).total_seconds() or 0.0001
previousTime = datetime.now()
# Prevent divison by zero
if not elapsed:
elapsed = 0.1**6
percent = done*100/total
progress["value"] = percent
rPercent = round(percent)
speed = (done/sinceStart)/10**6
# Prevent divison by zero
if not speed:
first = True
speed = 0.1**6
rSpeed = str(round(speed,2))
# Right-pad with zeros to large prevent layout shifts
while len(rSpeed.split(".")[1])!=2:
rSpeed += "0"
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
# If it's the first round and no data/predictions yet...
if first:
statusString.set("...% at ... MB/s (ETA: ...s)")
else:
# Update status
info = f"{rPercent}% at {rSpeed} MB/s (ETA: {eta}s)"
info = f"{percent:.0f}% at {speed:.2f} MB/s (ETA: {eta}s)"
if reedsolo and mode=="decrypt" and reedsoloFixedCount:
eng = "s" if reedsoloFixedCount!=1 else ""
info += f", fixed {reedsoloFixedCount} corrupted byte{eng}"
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)
# Flush outputs, close files
if not kept:
fout.flush()
fsync(fout.fileno())
fout.close()
fin.close()
# Securely wipe files as necessary
if wipe:
if draggedFolderPaths:
for i in draggedFolderPaths:
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])
secureWipe(inputFile)
# Secure wipe not enabled
else:
if allFiles:
# Remove temporary zip file if created
remove(inputFile)
# Show appropriate notice if file corrupted or modified
if not kept:
if mode=="encrypt":
output = inputFile.split("/")[-1]+".pcv"
else:
output = inputFile.split("/")[-1].replace(".pcv","")
statusString.set(f"Completed. (Output: {output})")
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:
statusString.set(f"Completed with {reedsoloFixedCount} bytes fixed."+
f" (Output: {output})")
statusString.set(
f"Completed with {reedsoloFixedCount}"+
f" bytes fixed. (Output: {output})"
)
else:
if kept=="modified":
statusString.set(kModifiedNotice)
@ -701,45 +795,32 @@ def start():
else:
statusString.set(kVeryCorruptedNotice)
status.config(cursor="hand2")
# A little hack since strings are immutable
output = "".join([i for i in outputFile])
# Bind the output file
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
selectFileInput["state"] = "normal"
adArea["state"] = "normal"
adArea.delete("1.0",tkinter.END)
adArea["state"] = "disabled"
startBtn["state"] = "disabled"
passwordInput["state"] = "normal"
passwordInput.delete(0,"end")
passwordInput["state"] = "disabled"
cpasswordInput["state"] = "normal"
cpasswordInput.delete(0,"end")
cpasswordInput["state"] = "disabled"
progress["value"] = 0
inputString.set("Please select a file.")
keepBtn["state"] = "normal"
keep.set(0)
keepBtn["state"] = "disabled"
eraseBtn["state"] = "normal"
erase.set(0)
eraseBtn["state"] = "disabled"
rs.set(0)
rsBtn["state"] = "disabled"
if not kept:
fout.flush()
fsync(fout.fileno())
fout.close()
fin.close()
if wipe:
# Make sure to flush file
wiper.flush()
fsync(wiper.fileno())
wiper.close()
remove(inputFile)
resetUI()
status["state"] = "normal"
inputFile = ""
outputFile = ""
password = ""
ad = ""
kept = False
working = False
allFiles = False
dragFolderPath = False
# Wipe keys for safety
del fin,fout,cipher,key
@ -751,34 +832,124 @@ def wrapper():
start()
except:
# Reset UI accordingly
progress.stop()
progress.config(mode="determinate")
progress["value"] = 100
selectFileInput["state"] = "normal"
passwordInput["state"] = "normal"
startBtn["state"] = "normal"
if gMode=="decrypt":
keepBtn["state"] = "normal"
resetDecryptionUI()
else:
adArea["state"] = "normal"
cpasswordInput["state"] = "normal"
rsBtn["state"] = "normal"
eraseBtn["state"] = "normal"
resetEncryptionUI()
statusString.set(unknownErrorNotice)
dummy.focus()
working = False
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.
# 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)
# Check platform, erase accordingly
if platform.system()=="Windows":
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)):
statusString.set(erasingNotice+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)
progress["value"] = 100
system(f'sdelete64.exe "{fin}" -p 4 -nobanner')
elif platform.system()=="Darwin":
system(f'rm -rfP "{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)
def resetUI():
adArea["state"] = "normal"
adArea.delete("1.0",tkinter.END)
adArea["state"] = "disabled"
adLabel["state"] = "disabled"
startBtn["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"
keep.set(0)
keepBtn["state"] = "disabled"
eraseBtn["state"] = "normal"
erase.set(0)
eraseBtn["state"] = "disabled"
rs.set(0)
rsBtn["state"] = "disabled"
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)}"')
# ad stands for "associated data"/metadata
adLabelString = tkinter.StringVar(tk)
adLabelString.set(adString)
@ -786,8 +957,9 @@ adLabel = tkinter.ttk.Label(
tk,
textvariable=adLabelString
)
adLabel.place(x=17,y=158)
adLabel.config(background="#f5f6f7")
adLabel.place(x=17,y=148)
adLabel.config(background="#ffffff")
adLabel["state"] = "disabled"
# Frame so metadata text box can fill width
adFrame = tkinter.Frame(
@ -795,7 +967,7 @@ adFrame = tkinter.Frame(
width=440,
height=100
)
adFrame.place(x=20,y=178)
adFrame.place(x=20,y=168)
adFrame.columnconfigure(0,weight=10)
adFrame.grid_propagate(False)
@ -818,7 +990,7 @@ keepBtn = tkinter.ttk.Checkbutton(
offvalue=0,
command=lambda:dummy.focus()
)
keepBtn.place(x=18,y=290)
keepBtn.place(x=18,y=280)
keepBtn["state"] = "disabled"
# Check box for securely erasing original file
@ -831,7 +1003,7 @@ eraseBtn = tkinter.ttk.Checkbutton(
offvalue=0,
command=lambda:dummy.focus()
)
eraseBtn.place(x=18,y=310)
eraseBtn.place(x=18,y=300)
eraseBtn["state"] = "disabled"
# Check box for Reed Solomon
@ -844,16 +1016,16 @@ rsBtn = tkinter.ttk.Checkbutton(
offvalue=0,
command=lambda:dummy.focus()
)
rsBtn.place(x=18,y=330)
rsBtn.place(x=18,y=320)
rsBtn["state"] = "disabled"
# Frame so start button can fill width
startFrame = tkinter.Frame(
tk,
width=442,
height=25
height=24
)
startFrame.place(x=19,y=360)
startFrame.place(x=19,y=350)
startFrame.columnconfigure(0,weight=10)
startFrame.grid_propagate(False)
# Start button
@ -872,7 +1044,7 @@ progress = tkinter.ttk.Progressbar(
length=440,
mode="determinate"
)
progress.place(x=20,y=388)
progress.place(x=20,y=378)
# Status label
statusString = tkinter.StringVar(tk)
@ -881,8 +1053,9 @@ status = tkinter.ttk.Label(
tk,
textvariable=statusString
)
status.place(x=17,y=416)
status.config(background="#f5f6f7")
status.place(x=17,y=406)
status.config(background="#ffffff")
status["state"] = "disabled"
# Credits :)
hint = "Created by Evan Su. Click for details and source."
@ -894,21 +1067,21 @@ credits = tkinter.ttk.Label(
cursor="hand2"
)
credits["state"] = "disabled"
credits.config(background="#f5f6f7")
credits.place(x=17,y=446)
credits.config(background="#ffffff")
credits.place(x=17,y=436)
source = "https://github.com/HACKERALERT/Picocrypt"
credits.bind("<Button-1>",lambda e:webbrowser.open(source))
# Version
versionString = tkinter.StringVar(tk)
versionString.set("v1.10")
versionString.set("v1.11")
version = tkinter.ttk.Label(
tk,
textvariable=versionString
)
version["state"] = "disabled"
version.config(background="#f5f6f7")
version.place(x=430,y=446)
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
@ -923,16 +1096,28 @@ def createRsc():
headerRsc = 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()
# 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()