1
0
Fork 0
mirror of https://github.com/HACKERALERT/Picocrypt.git synced 2024-09-20 01:36:55 +00:00
Picocrypt/src/Picocrypt.py
2021-03-23 20:02:00 -04:00

1125 lines
28 KiB
Python

#!/usr/bin/env python3
"""
Picocrypt v1.11
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 ~
"""
# Imports
from tkinter import filedialog,messagebox
from threading import Thread
from datetime import datetime
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,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
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
rootDir = dirname(realpath(__file__))
inputFile = ""
outputFile = ""
outputPath = ""
password = ""
ad = ""
kept = False
working = False
gMode = 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."
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."
# Create root Tk
tk = TkinterDnD.Tk()
tk.geometry("480x470")
tk.title("Picocrypt")
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
try:
favicon = tkinter.PhotoImage(file="./key.png")
tk.iconphoto(False,favicon)
except:
pass
# Some styling
s = tkinter.ttk.Style()
s.configure("TCheckbutton",background="#ffffff")
# 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)
# Use try to handle errors
try:
# Create list of input files
allFiles = []
files = []
draggedFolderPaths = []
suffix = ""
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 inputFile.endswith(".pcv"):
suffix = " (will decrypt)"
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.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"
# 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)
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)
# 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.
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
except UnicodeDecodeError:
statusString.set(corruptedNotice)
progress["value"] = 100
# No file(s) selected, do nothing
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
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("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 ".pcv" not in inputFile:
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
disableAllInputs()
# Make sure passwords match
if passwordInput.get()!=cpasswordInput.get() and mode=="encrypt":
resetEncryptionUI()
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()
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
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.")
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)
# 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
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))
# 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
try:
cs = bytes(headerRsc.decode(cs)[0])
except:
headerBroken = True
cs = cs[:64]
try:
crccs = bytes(headerRsc.decode(crccs)[0])
except:
headerBroken = True
crccs = crccs[:64]
try:
digest = bytes(headerRsc.decode(digest)[0])
except:
headerBroken = True
digest = digest[:16]
try:
salt = bytes(headerRsc.decode(salt)[0])
except:
headerBroken = True
salt = salt[:16]
try:
nonce = bytes(headerRsc.decode(nonce)[0])
except:
headerBroken = True
nonce = nonce[:24]
if headerBroken:
if keep.get()!=1:
statusString.set(veryCorruptedNotice)
fin.close()
fout.close()
remove(outputFile)
# Reset UI
resetDecryptionUI()
return
else:
kept = "badlyCorrupted"
# Show notice about key derivation
statusString.set(derivingNotice)
# Derive argon2id key
key = hash_secret_raw(
password,
salt,
time_cost=8, # 8 iterations
memory_cost=2**20, # 2^20 Kibibytes (1GiB)
parallelism=8, # 8 parallel threads
hash_len=32,
type=Type.ID
)
# 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
if mode=="decrypt":
# If key is incorrect...
if not compare_digest(check,cs):
if not headerBroken:
statusString.set(passwordNotice)
fin.close()
fout.close()
remove(outputFile)
# Reset UI
resetDecryptionUI()
return
# Create XChaCha20-Poly1305 object
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
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
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))
else:
# Basically encrypting but in reverse
if reedsolo:
try:
data,_,fixed = rsc.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:
fin.close()
fout.close()
remove(outputFile)
# Reset UI
resetDecryptionUI()
del fin,fout,cipher,key
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)
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)
# 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:
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}"+
f" bytes fixed. (Output: {output})"
)
else:
if kept=="modified":
statusString.set(kModifiedNotice)
elif kept=="corrupted":
statusString.set(kCorruptedNotice)
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
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
# Wraps the start() function with error handling
def wrapper():
global working,gMode
# Try start() and handle errors
try:
start()
except:
# Reset UI accordingly
if gMode=="decrypt":
resetDecryptionUI()
else:
resetEncryptionUI()
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)
# 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)
adLabel = tkinter.ttk.Label(
tk,
textvariable=adLabelString
)
adLabel.place(x=17,y=148)
adLabel.config(background="#ffffff")
adLabel["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("<Button-1>",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)
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()
sys.exit(0)