Update Picocrypt.py
This commit is contained in:
parent
2df0fecf8f
commit
ee5eeacd7b
|
@ -59,7 +59,7 @@ reedsolo = False
|
||||||
reedsoloFixed = False
|
reedsoloFixed = False
|
||||||
reedsoloErrors = False
|
reedsoloErrors = False
|
||||||
|
|
||||||
# Strings
|
# A list of strings and notices
|
||||||
strings = [
|
strings = [
|
||||||
"File metadata (optional):",
|
"File metadata (optional):",
|
||||||
"Compressing files together...",
|
"Compressing files together...",
|
||||||
|
@ -84,51 +84,30 @@ strings = [
|
||||||
"Error. The input file couldn't be decoded as UTF-8."
|
"Error. The input file couldn't be decoded as UTF-8."
|
||||||
]
|
]
|
||||||
|
|
||||||
# Create root tk
|
# Create root window
|
||||||
tk = TkinterDnD.Tk()
|
tk = TkinterDnD.Tk()
|
||||||
tk.geometry("480x500")
|
tk.geometry("480x500")
|
||||||
tk.title("Picocrypt")
|
tk.title("Picocrypt")
|
||||||
tk.resizable(0,0)
|
tk.resizable(0,0)
|
||||||
tk.configure(background="#f5f6f7")
|
tk.configure(background="#f5f6f7")
|
||||||
|
|
||||||
|
# Set "arc" theme
|
||||||
ThemedStyle(tk).set_theme("arc")
|
ThemedStyle(tk).set_theme("arc")
|
||||||
|
|
||||||
# Enable high DPI on Windows
|
# Disable high DPI on Windows to prevent ugly scaling
|
||||||
def Get_HWND_DPI(window_handle):
|
try:
|
||||||
from ctypes import windll,pointer,wintypes
|
from ctypes import windll
|
||||||
windll.shcore.SetProcessDpiAwareness(1)
|
windll.shcore.SetProcessDpiAwareness(0)
|
||||||
DPI100pc = 96
|
|
||||||
DPI_type = 0
|
|
||||||
winH = wintypes.HWND(window_handle)
|
|
||||||
monitorhandle = windll.user32.MonitorFromWindow(
|
|
||||||
winH,wintypes.DWORD(2)
|
|
||||||
)
|
|
||||||
X = wintypes.UINT()
|
|
||||||
Y = wintypes.UINT()
|
|
||||||
try:
|
|
||||||
windll.shcore.GetDpiForMonitor(
|
|
||||||
monitorhandle,DPI_type,pointer(X),pointer(Y)
|
|
||||||
)
|
|
||||||
return X.value*2,Y.value*2,(X.value+Y.value)/(2*DPI100pc)
|
|
||||||
except Exception:
|
|
||||||
return 96,96,1
|
|
||||||
def TkGeometryScale(s,cvtfunc):
|
|
||||||
patt = r"(?P<W>\d+)x(?P<H>\d+)\+(?P<X>\d+)\+(?P<Y>\d+)"
|
|
||||||
R = re.compile(patt).search(s)
|
|
||||||
G = str(cvtfunc(R.group("W")))+"x"
|
|
||||||
G += str(cvtfunc(R.group("H")))+"+"
|
|
||||||
G += str(cvtfunc(R.group("X")))+"+"
|
|
||||||
G += str(cvtfunc(R.group("Y")))
|
|
||||||
return G
|
|
||||||
def MakeTkDPIAware(TKGUI):
|
|
||||||
TKGUI.DPI_X,TKGUI.DPI_Y,TKGUI.DPI_scaling = Get_HWND_DPI(TKGUI.winfo_id())
|
|
||||||
TKGUI.TkScale = lambda v:int(float(v)*TKGUI.DPI_scaling)
|
|
||||||
TKGUI.TkGeometryScale = lambda s:TkGeometryScale(s,TKGUI.TkScale)
|
|
||||||
if platform.system()=="Windows":
|
|
||||||
pass#MakeTkDPIAware(tk)
|
|
||||||
|
|
||||||
# Try setting window icon if it exists
|
# Try setting window icon if it exists
|
||||||
try:
|
try:
|
||||||
favicon = tkinter.PhotoImage(file="./key.png")
|
try:
|
||||||
|
# PyInstaller bundles files in _MEIPASS
|
||||||
|
from sys import _MEIPASS
|
||||||
|
favicon = tkinter.PhotoImage(file=pathJoin(_MEIPASS,"key.png"))
|
||||||
|
except:
|
||||||
|
# PyInstaller not used, load from same directory
|
||||||
|
favicon = tkinter.PhotoImage(file="key.png")
|
||||||
tk.iconphoto(False,favicon)
|
tk.iconphoto(False,favicon)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -171,7 +150,6 @@ outputLabel = tkinter.ttk.Label(
|
||||||
textvariable=outputString
|
textvariable=outputString
|
||||||
)
|
)
|
||||||
outputLabel.place(x=20,y=51)
|
outputLabel.place(x=20,y=51)
|
||||||
|
|
||||||
outputLabel["state"] = "disabled"
|
outputLabel["state"] = "disabled"
|
||||||
|
|
||||||
# A ".pcv" extension shown next to output box
|
# A ".pcv" extension shown next to output box
|
||||||
|
@ -198,6 +176,7 @@ outputInput = tkinter.ttk.Entry(outputFrame)
|
||||||
outputInput.grid(sticky="nesw")
|
outputInput.grid(sticky="nesw")
|
||||||
outputInput["state"] = "disabled"
|
outputInput["state"] = "disabled"
|
||||||
|
|
||||||
|
# A "or" label between output box and "Save as" button
|
||||||
orString = tkinter.StringVar(tk)
|
orString = tkinter.StringVar(tk)
|
||||||
orString.set("or")
|
orString.set("or")
|
||||||
orLabel = tkinter.ttk.Label(
|
orLabel = tkinter.ttk.Label(
|
||||||
|
@ -206,15 +185,20 @@ orLabel = tkinter.ttk.Label(
|
||||||
)
|
)
|
||||||
orLabel.place(x=356,y=71)
|
orLabel.place(x=356,y=71)
|
||||||
|
|
||||||
|
# Save as custom file
|
||||||
def saveAs():
|
def saveAs():
|
||||||
global mode,onlyFiles,onlyFolders
|
global mode,onlyFiles,onlyFolders
|
||||||
dummy.focus()
|
dummy.focus()
|
||||||
|
|
||||||
|
# Get the root directory to ask user to save as
|
||||||
if inputFile:
|
if inputFile:
|
||||||
saveDir = dirname(inputFile)
|
saveDir = dirname(inputFile)
|
||||||
elif onlyFiles:
|
elif onlyFiles:
|
||||||
saveDir = dirname(onlyFiles[0])
|
saveDir = dirname(onlyFiles[0])
|
||||||
else:
|
else:
|
||||||
saveDir = Path(onlyFolders[0]).parent.absolute()
|
saveDir = Path(onlyFolders[0]).parent.absolute()
|
||||||
|
|
||||||
|
# Ask user to save file
|
||||||
tmp = asksaveasfilename(
|
tmp = asksaveasfilename(
|
||||||
initialdir=saveDir,
|
initialdir=saveDir,
|
||||||
initialfile=(
|
initialfile=(
|
||||||
|
@ -222,9 +206,13 @@ def saveAs():
|
||||||
),
|
),
|
||||||
confirmoverwrite=True
|
confirmoverwrite=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# If a custom output is chosen, update the output box accordingly
|
||||||
if tmp:
|
if tmp:
|
||||||
outputInput.delete(0,tkinter.END)
|
outputInput.delete(0,tkinter.END)
|
||||||
outputInput.insert(0,(tmp if mode=="decrypt" else tmp[:-4]))
|
outputInput.insert(0,(tmp if mode=="decrypt" else tmp[:-4]))
|
||||||
|
|
||||||
|
# Button to allow custom output
|
||||||
saveAsBtn = tkinter.ttk.Button(
|
saveAsBtn = tkinter.ttk.Button(
|
||||||
tk,
|
tk,
|
||||||
text="Save as",
|
text="Save as",
|
||||||
|
@ -261,6 +249,7 @@ passwordInput = tkinter.ttk.Entry(
|
||||||
passwordInput.grid(sticky="nesw")
|
passwordInput.grid(sticky="nesw")
|
||||||
passwordInput["state"] = "disabled"
|
passwordInput["state"] = "disabled"
|
||||||
|
|
||||||
|
# Toggle password visibility
|
||||||
def showPassword(e):
|
def showPassword(e):
|
||||||
if passwordInput.cget("show"):
|
if passwordInput.cget("show"):
|
||||||
passwordInput.config(show="")
|
passwordInput.config(show="")
|
||||||
|
@ -269,6 +258,7 @@ def showPassword(e):
|
||||||
passwordInput.config(show="•")
|
passwordInput.config(show="•")
|
||||||
cPasswordInput.config(show="•")
|
cPasswordInput.config(show="•")
|
||||||
|
|
||||||
|
# Eye icon to show password
|
||||||
passwordShowString = tkinter.StringVar(tk)
|
passwordShowString = tkinter.StringVar(tk)
|
||||||
passwordShowString.set("👁")
|
passwordShowString.set("👁")
|
||||||
passwordShow = tkinter.ttk.Label(
|
passwordShow = tkinter.ttk.Label(
|
||||||
|
@ -343,19 +333,26 @@ def doPasswordsMatch():
|
||||||
if mode=="decrypt":
|
if mode=="decrypt":
|
||||||
return
|
return
|
||||||
matches = passwordInput.get()==cPasswordInput.get()
|
matches = passwordInput.get()==cPasswordInput.get()
|
||||||
|
|
||||||
|
# Passwords match
|
||||||
if passwordInput.get() and matches:
|
if passwordInput.get() and matches:
|
||||||
passwordMatchesString.set("✔️")
|
passwordMatchesString.set("✔️")
|
||||||
startBtn["state"] = "normal"
|
startBtn["state"] = "normal"
|
||||||
startBtn.config(cursor="hand2")
|
startBtn.config(cursor="hand2")
|
||||||
|
|
||||||
|
# Passwords don't match
|
||||||
elif passwordInput.get() and not matches:
|
elif passwordInput.get() and not matches:
|
||||||
passwordMatchesString.set("❌")
|
passwordMatchesString.set("❌")
|
||||||
startBtn["state"] = "disabled"
|
startBtn["state"] = "disabled"
|
||||||
startBtn.config(cursor="")
|
startBtn.config(cursor="")
|
||||||
|
|
||||||
|
# Password is empty
|
||||||
elif not passwordInput.get():
|
elif not passwordInput.get():
|
||||||
passwordMatchesString.set("")
|
passwordMatchesString.set("")
|
||||||
startBtn["state"] = "disabled"
|
startBtn["state"] = "disabled"
|
||||||
startBtn.config(cursor="")
|
startBtn.config(cursor="")
|
||||||
|
|
||||||
|
# Bind key releases to appropriate functions
|
||||||
passwordInput.bind("<KeyRelease>",lambda e:[showStrength(),doPasswordsMatch()])
|
passwordInput.bind("<KeyRelease>",lambda e:[showStrength(),doPasswordsMatch()])
|
||||||
cPasswordInput.bind("<KeyRelease>",lambda e:doPasswordsMatch())
|
cPasswordInput.bind("<KeyRelease>",lambda e:doPasswordsMatch())
|
||||||
|
|
||||||
|
@ -368,7 +365,7 @@ passwordStrength = tkinter.Frame(
|
||||||
passwordStrength.config(bg="#e5eaf0")
|
passwordStrength.config(bg="#e5eaf0")
|
||||||
passwordStrength.place(x=21,y=146)
|
passwordStrength.place(x=21,y=146)
|
||||||
|
|
||||||
# Check box that indicates if password match
|
# Label that indicates if passwords match
|
||||||
passwordMatchesString = tkinter.StringVar(tk)
|
passwordMatchesString = tkinter.StringVar(tk)
|
||||||
passwordMatches = tkinter.ttk.Label(
|
passwordMatches = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
|
@ -410,6 +407,8 @@ metadataInput.grid(row=0,column=0,sticky="nesw",padx=1,pady=1)
|
||||||
metadataInput.config(borderwidth=0)
|
metadataInput.config(borderwidth=0)
|
||||||
metadataInput.config(bg="#fbfcfc")
|
metadataInput.config(bg="#fbfcfc")
|
||||||
metadataInput["state"] = "disabled"
|
metadataInput["state"] = "disabled"
|
||||||
|
|
||||||
|
# Tkinter's Text() boxes are ugly, so I (painfully) beautify it manually
|
||||||
metadataInput.bind(
|
metadataInput.bind(
|
||||||
"<FocusIn>",
|
"<FocusIn>",
|
||||||
lambda e:metadataBoxUI("in")
|
lambda e:metadataBoxUI("in")
|
||||||
|
@ -418,7 +417,6 @@ metadataInput.bind(
|
||||||
"<FocusOut>",
|
"<FocusOut>",
|
||||||
lambda e:metadataBoxUI("out")
|
lambda e:metadataBoxUI("out")
|
||||||
)
|
)
|
||||||
# Tkinter's Text() boxes are ugly, so I beautify it manually
|
|
||||||
def metadataBoxUI(what):
|
def metadataBoxUI(what):
|
||||||
if what=="in":
|
if what=="in":
|
||||||
if metadataInput.cget("bg")=="#ffffff":
|
if metadataInput.cget("bg")=="#ffffff":
|
||||||
|
@ -439,7 +437,7 @@ keepBtn = tkinter.ttk.Checkbutton(
|
||||||
keepBtn.place(x=17,y=337)
|
keepBtn.place(x=17,y=337)
|
||||||
keepBtn["state"] = "disabled"
|
keepBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Check box for securely erasing original files
|
# Check box for securely erasing original file(s)
|
||||||
erase = tkinter.IntVar(tk)
|
erase = tkinter.IntVar(tk)
|
||||||
eraseBtn = tkinter.ttk.Checkbutton(
|
eraseBtn = tkinter.ttk.Checkbutton(
|
||||||
tk,
|
tk,
|
||||||
|
@ -498,9 +496,11 @@ startBtn = tkinter.ttk.Button(
|
||||||
startBtn.grid(row=0,column=0,stick="nesw")
|
startBtn.grid(row=0,column=0,stick="nesw")
|
||||||
startBtn["state"] = "disabled"
|
startBtn["state"] = "disabled"
|
||||||
|
|
||||||
|
# Cancel encrypting/decrypting
|
||||||
def cancel():
|
def cancel():
|
||||||
global working
|
global working
|
||||||
working = False
|
working = False
|
||||||
|
|
||||||
# Cancel button
|
# Cancel button
|
||||||
cancelBtn = tkinter.ttk.Button(
|
cancelBtn = tkinter.ttk.Button(
|
||||||
startFrame,
|
startFrame,
|
||||||
|
@ -520,7 +520,7 @@ progress = tkinter.ttk.Progressbar(
|
||||||
)
|
)
|
||||||
progress.place(x=30,y=420)#.place(x=20,y=448)
|
progress.place(x=30,y=420)#.place(x=20,y=448)
|
||||||
|
|
||||||
# Lift start frame in front of progress bar
|
# Lift start and cancel button in front of progress bar
|
||||||
startFrame.lift()
|
startFrame.lift()
|
||||||
|
|
||||||
# Status label
|
# Status label
|
||||||
|
@ -556,11 +556,9 @@ version = tkinter.ttk.Label(
|
||||||
version["state"] = "disabled"
|
version["state"] = "disabled"
|
||||||
version.place(x=430,y=468)
|
version.place(x=430,y=468)
|
||||||
|
|
||||||
# Drag files window
|
# Drag files indicator window
|
||||||
prompt = tkinter.Frame(tk)
|
prompt = tkinter.Frame(tk)
|
||||||
prompt.config(bg="#f5f6f7")
|
prompt.config(bg="#f5f6f7")
|
||||||
#prompt.pack(expand=1,fill=tkinter.BOTH)
|
|
||||||
|
|
||||||
promptString = tkinter.StringVar(tk)
|
promptString = tkinter.StringVar(tk)
|
||||||
promptString.set("Drag and drop file(s) and folder(s) here.")
|
promptString.set("Drag and drop file(s) and folder(s) here.")
|
||||||
promptLabel = tkinter.ttk.Label(
|
promptLabel = tkinter.ttk.Label(
|
||||||
|
@ -581,9 +579,9 @@ promptIconVer = tkinter.Frame(
|
||||||
)
|
)
|
||||||
promptIconVer.place(x=238,y=231,height=64)
|
promptIconVer.place(x=238,y=231,height=64)
|
||||||
|
|
||||||
|
# Ask user to confirm overwrite
|
||||||
confirmOverwrite = tkinter.Frame(tk)
|
confirmOverwrite = tkinter.Frame(tk)
|
||||||
confirmOverwrite.config(bg="#f5f6f7")
|
confirmOverwrite.config(bg="#f5f6f7")
|
||||||
|
|
||||||
confirmOverwriteString = tkinter.StringVar(tk)
|
confirmOverwriteString = tkinter.StringVar(tk)
|
||||||
confirmOverwriteString.set(strings[13])
|
confirmOverwriteString.set(strings[13])
|
||||||
confirmOverwriteLabel = tkinter.ttk.Label(
|
confirmOverwriteLabel = tkinter.ttk.Label(
|
||||||
|
@ -599,6 +597,7 @@ confirmOverwriteNo = tkinter.ttk.Button(
|
||||||
)
|
)
|
||||||
confirmOverwriteNo.place(x=150,y=245)
|
confirmOverwriteNo.place(x=150,y=245)
|
||||||
|
|
||||||
|
# Start encryption/decryption after user confirms
|
||||||
def overwriteConfirmed():
|
def overwriteConfirmed():
|
||||||
confirmOverwrite.pack_forget()
|
confirmOverwrite.pack_forget()
|
||||||
Thread(target=wrapper,daemon=True,args=(True,)).start()
|
Thread(target=wrapper,daemon=True,args=(True,)).start()
|
||||||
|
@ -617,6 +616,7 @@ def filesDragged(draggedFiles):
|
||||||
resetUI()
|
resetUI()
|
||||||
status.config(cursor="")
|
status.config(cursor="")
|
||||||
status.bind("<Button-1>",lambda e:None)
|
status.bind("<Button-1>",lambda e:None)
|
||||||
|
|
||||||
# Use try to catch file errors
|
# Use try to catch file errors
|
||||||
try:
|
try:
|
||||||
# Create lists to track files dragged
|
# Create lists to track files dragged
|
||||||
|
@ -689,11 +689,15 @@ def filesDragged(draggedFiles):
|
||||||
metadataLength = bytes(rs128.decode(metadataLength)[0])
|
metadataLength = bytes(rs128.decode(metadataLength)[0])
|
||||||
metadataLength = metadataLength.replace(b"+",b"")
|
metadataLength = metadataLength.replace(b"+",b"")
|
||||||
metadata = fin.read(int(metadataLength.decode("utf-8")))
|
metadata = fin.read(int(metadataLength.decode("utf-8")))
|
||||||
metadata = bytes(rs128.decode(metadata)[0]).decode("utf-8")
|
|
||||||
metadataString.set("File metadata (read only):")
|
metadataString.set("File metadata (read only):")
|
||||||
metadataInput["state"] = "normal"
|
metadataInput["state"] = "normal"
|
||||||
metadataInput.delete("1.0",tkinter.END)
|
metadataInput.delete("1.0",tkinter.END)
|
||||||
metadataInput.insert("1.0",metadata)
|
try:
|
||||||
|
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"
|
metadataInput["state"] = "disabled"
|
||||||
fin.close()
|
fin.close()
|
||||||
|
|
||||||
|
@ -766,11 +770,14 @@ def onDropEnter(e):
|
||||||
prompt.lift()
|
prompt.lift()
|
||||||
def onDropLeave(e):
|
def onDropLeave(e):
|
||||||
prompt.pack_forget()
|
prompt.pack_forget()
|
||||||
|
|
||||||
|
# Bind functions to window
|
||||||
tk.drop_target_register(DND_FILES)
|
tk.drop_target_register(DND_FILES)
|
||||||
tk.dnd_bind("<<Drop>>",onDrop)
|
tk.dnd_bind("<<Drop>>",onDrop)
|
||||||
tk.dnd_bind("<<DropEnter>>",onDropEnter)
|
tk.dnd_bind("<<DropEnter>>",onDropEnter)
|
||||||
tk.dnd_bind("<<DropLeave>>",onDropLeave)
|
tk.dnd_bind("<<DropLeave>>",onDropLeave)
|
||||||
|
|
||||||
|
# Main encryption/decryption function
|
||||||
def work():
|
def work():
|
||||||
global inputFile,outputFile,working,mode,rs13,rs128,reedsolo
|
global inputFile,outputFile,working,mode,rs13,rs128,reedsolo
|
||||||
global done,stopUpdating,startTime,previousTime,onlyFiles
|
global done,stopUpdating,startTime,previousTime,onlyFiles
|
||||||
|
@ -818,7 +825,6 @@ def work():
|
||||||
zf.write(i,i[zfOffset:])
|
zf.write(i,i[zfOffset:])
|
||||||
for i in onlyFiles:
|
for i in onlyFiles:
|
||||||
zf.write(i,pathSplit(i)[1])
|
zf.write(i,pathSplit(i)[1])
|
||||||
|
|
||||||
zf.close()
|
zf.close()
|
||||||
inputFile = zfName
|
inputFile = zfName
|
||||||
outputFile = zfName+".pcv"
|
outputFile = zfName+".pcv"
|
||||||
|
@ -832,27 +838,34 @@ def work():
|
||||||
statusString.set(strings[16])
|
statusString.set(strings[16])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# If encrypting, generate values for encryption
|
||||||
if mode=="encrypt":
|
if mode=="encrypt":
|
||||||
salt = urandom(16)
|
salt = urandom(16) # Argon2 salt
|
||||||
nonce = urandom(24)
|
nonce = urandom(24) # XChaCha20 nonce
|
||||||
fout = open(outputFile,"wb+")
|
fout = open(outputFile,"wb+")
|
||||||
|
|
||||||
|
# Indicate Reed-Solomon with "+"
|
||||||
if reedsolo:
|
if reedsolo:
|
||||||
fout.write(rs128.encode(b"+"))
|
fout.write(rs128.encode(b"+"))
|
||||||
else:
|
else:
|
||||||
fout.write(rs128.encode(b"-"))
|
fout.write(rs128.encode(b"-"))
|
||||||
|
|
||||||
|
# Encode metadata and length of metadata
|
||||||
metadata = rs128.encode(metadata)
|
metadata = rs128.encode(metadata)
|
||||||
tmp = len(metadata)
|
tmp = len(metadata)
|
||||||
tmp = f"{tmp:+<10}"
|
tmp = f"{tmp:+<10}"
|
||||||
tmp = rs128.encode(tmp.encode("utf-8"))
|
tmp = rs128.encode(tmp.encode("utf-8"))
|
||||||
|
|
||||||
|
# Write to file
|
||||||
fout.write(tmp)
|
fout.write(tmp)
|
||||||
fout.write(metadata)
|
fout.write(metadata)
|
||||||
fout.write(rs128.encode(salt)) # Argon2 salt
|
fout.write(rs128.encode(salt)) # Argon2 salt
|
||||||
fout.write(rs128.encode(nonce)) # ChaCha20 nonce
|
fout.write(rs128.encode(nonce)) # XChaCha20 nonce
|
||||||
fout.write(b"0"*192) # Hash of key
|
fout.write(b"0"*192) # Hash of key
|
||||||
fout.write(b"0"*144) # Poly1305 MAC
|
fout.write(b"0"*144) # Poly1305 MAC
|
||||||
fout.write(b"0"*160) # BLAKE3 CRC
|
fout.write(b"0"*160) # BLAKE3 CRC
|
||||||
|
|
||||||
|
# If decrypting, read values from file
|
||||||
else:
|
else:
|
||||||
tmp = fin.read(129)
|
tmp = fin.read(129)
|
||||||
if bytes(rs128.decode(tmp)[0])==b"+":
|
if bytes(rs128.decode(tmp)[0])==b"+":
|
||||||
|
@ -865,12 +878,14 @@ def work():
|
||||||
metadataLength = metadataLength.replace(b"+",b"")
|
metadataLength = metadataLength.replace(b"+",b"")
|
||||||
fin.read(int(metadataLength.decode("utf-8")))
|
fin.read(int(metadataLength.decode("utf-8")))
|
||||||
|
|
||||||
|
# Read values
|
||||||
salt = fin.read(144)
|
salt = fin.read(144)
|
||||||
nonce = fin.read(152)
|
nonce = fin.read(152)
|
||||||
keycs = fin.read(192)
|
keycs = fin.read(192)
|
||||||
maccs = fin.read(144)
|
maccs = fin.read(144)
|
||||||
crccs = fin.read(160)
|
crccs = fin.read(160)
|
||||||
|
|
||||||
|
# Try to decode each value, increase Reed-Solomon errors fixed if needed
|
||||||
try:
|
try:
|
||||||
salt,_,fixed = rs128.decode(salt)
|
salt,_,fixed = rs128.decode(salt)
|
||||||
salt = bytes(salt)
|
salt = bytes(salt)
|
||||||
|
@ -902,7 +917,9 @@ def work():
|
||||||
except:
|
except:
|
||||||
headerBroken = True
|
headerBroken = True
|
||||||
|
|
||||||
|
# If the header is broken...
|
||||||
if headerBroken:
|
if headerBroken:
|
||||||
|
# Stop if user chose not to keep broken output
|
||||||
if not shouldKeep:
|
if not shouldKeep:
|
||||||
statusString.set(strings[8])
|
statusString.set(strings[8])
|
||||||
fin.close()
|
fin.close()
|
||||||
|
@ -915,6 +932,7 @@ def work():
|
||||||
|
|
||||||
statusString.set(strings[9])
|
statusString.set(strings[9])
|
||||||
|
|
||||||
|
# Generate Argon2d key from master password
|
||||||
key = hash_secret_raw(
|
key = hash_secret_raw(
|
||||||
password,
|
password,
|
||||||
salt,
|
salt,
|
||||||
|
@ -925,29 +943,41 @@ def work():
|
||||||
type=argonType.D
|
type=argonType.D
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Stop the indeterminate progress bar and set determinate
|
||||||
progress.stop()
|
progress.stop()
|
||||||
progress.config(mode="determinate")
|
progress.config(mode="determinate")
|
||||||
progress["value"] = 0
|
progress["value"] = 0
|
||||||
|
|
||||||
|
# Hash of the derived Argon2 key
|
||||||
check = SHA3_512.new(data=key).digest()
|
check = SHA3_512.new(data=key).digest()
|
||||||
|
|
||||||
|
# Check if password is correct
|
||||||
if mode=="decrypt":
|
if mode=="decrypt":
|
||||||
if not compare_digest(check,keycs):
|
if not compare_digest(check,keycs):
|
||||||
|
# If header isn't broken...
|
||||||
if not headerBroken:
|
if not headerBroken:
|
||||||
|
# Tell user password is incorrect
|
||||||
statusString.set(strings[2])
|
statusString.set(strings[2])
|
||||||
fin.close()
|
fin.close()
|
||||||
setDecryptionUI()
|
setDecryptionUI()
|
||||||
return
|
return
|
||||||
fout = open(outputFile,"wb+")
|
fout = open(outputFile,"wb+")
|
||||||
|
|
||||||
crc = blake3()#BLAKE2b.new(digest_bits=512)
|
crc = blake3() # Blake3 CRC
|
||||||
cipher = ChaCha20_Poly1305.new(key=key,nonce=nonce)
|
cipher = ChaCha20_Poly1305.new(key=key,nonce=nonce) # XChaCha20
|
||||||
|
|
||||||
|
# Variables for calculating speeds, etc.
|
||||||
done = 0
|
done = 0
|
||||||
total = getsize(inputFile)
|
total = getsize(inputFile)
|
||||||
startTime = datetime.now()
|
startTime = datetime.now()
|
||||||
previousTime = datetime.now()
|
previousTime = datetime.now()
|
||||||
|
|
||||||
|
# Update progress bar, etc. in another thread
|
||||||
Thread(target=updateStats,daemon=True,args=(total,)).start()
|
Thread(target=updateStats,daemon=True,args=(total,)).start()
|
||||||
|
|
||||||
|
# Start the encryption/decryption process
|
||||||
while True:
|
while True:
|
||||||
|
# Check if cancel button pressed
|
||||||
if not working:
|
if not working:
|
||||||
fin.close()
|
fin.close()
|
||||||
fout.close()
|
fout.close()
|
||||||
|
@ -960,18 +990,24 @@ def work():
|
||||||
dummy.focus()
|
dummy.focus()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Read from file, read extra if Reed-Solomon was enabled
|
||||||
if mode=="decrypt" and reedsolo:
|
if mode=="decrypt" and reedsolo:
|
||||||
piece = fin.read(1104905)
|
piece = fin.read(1104905)
|
||||||
else:
|
else:
|
||||||
piece = fin.read(2**20)
|
piece = fin.read(2**20)
|
||||||
|
|
||||||
|
# End of file
|
||||||
if not piece:
|
if not piece:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Encrypt, etc.
|
||||||
if mode=="encrypt":
|
if mode=="encrypt":
|
||||||
data = cipher.encrypt(piece)
|
data = cipher.encrypt(piece)
|
||||||
if reedsolo:
|
if reedsolo:
|
||||||
data = bytes(rs13.encode(data))
|
data = bytes(rs13.encode(data))
|
||||||
crc.update(data)
|
crc.update(data)
|
||||||
|
|
||||||
|
# Decrypt, etc.
|
||||||
else:
|
else:
|
||||||
crc.update(piece)
|
crc.update(piece)
|
||||||
if reedsolo:
|
if reedsolo:
|
||||||
|
@ -987,17 +1023,17 @@ def work():
|
||||||
setDecryptionUI()
|
setDecryptionUI()
|
||||||
return
|
return
|
||||||
|
|
||||||
kept = "badlyCorrupted"
|
|
||||||
# Attempt to recover badly corrupted data
|
# Attempt to recover badly corrupted data
|
||||||
|
kept = "badlyCorrupted"
|
||||||
data = b""
|
data = b""
|
||||||
piece = piece[:-13]
|
piece = piece[:-13]
|
||||||
counter = 0
|
counter = 0
|
||||||
while True:
|
while True:
|
||||||
# Basically just strip the Reed-Solomon bytes
|
# Basically just strip off the Reed-Solomon bytes
|
||||||
# and return the original non-encoded data
|
# and return the original non-encoded data
|
||||||
if counter<1104905:
|
if counter<1104905:
|
||||||
data += piece[counter:counter+242]
|
data += piece[counter:counter+242]
|
||||||
counter += 255 # 255 bytes, 242 original
|
counter += 255 # 242 bytes + 13 Reed-Solomon
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
fixed = bytearray()
|
fixed = bytearray()
|
||||||
|
@ -1009,9 +1045,11 @@ def work():
|
||||||
else:
|
else:
|
||||||
data = cipher.decrypt(piece)
|
data = cipher.decrypt(piece)
|
||||||
|
|
||||||
|
# Write the data, increase the amount done
|
||||||
fout.write(data)
|
fout.write(data)
|
||||||
done += 2**20
|
done += 2**20
|
||||||
|
|
||||||
|
# Encryption is done, write appropriate values to file
|
||||||
if mode=="encrypt":
|
if mode=="encrypt":
|
||||||
fout.flush()
|
fout.flush()
|
||||||
fout.close()
|
fout.close()
|
||||||
|
@ -1020,12 +1058,14 @@ def work():
|
||||||
fout.write(rs128.encode(check))
|
fout.write(rs128.encode(check))
|
||||||
fout.write(rs128.encode(cipher.digest()))
|
fout.write(rs128.encode(cipher.digest()))
|
||||||
fout.write(rs128.encode(crc.digest()))
|
fout.write(rs128.encode(crc.digest()))
|
||||||
|
|
||||||
|
# Decryption is done, check for integrity and authenticity
|
||||||
else:
|
else:
|
||||||
|
# File is corrupted
|
||||||
if not compare_digest(crccs,crc.digest()):
|
if not compare_digest(crccs,crc.digest()):
|
||||||
statusString.set(strings[3])
|
statusString.set(strings[3])
|
||||||
fin.close()
|
fin.close()
|
||||||
fout.close()
|
fout.close()
|
||||||
|
|
||||||
if keep.get()!=1:
|
if keep.get()!=1:
|
||||||
remove(outputFile)
|
remove(outputFile)
|
||||||
setDecryptionUI()
|
setDecryptionUI()
|
||||||
|
@ -1033,7 +1073,6 @@ def work():
|
||||||
else:
|
else:
|
||||||
if not kept:
|
if not kept:
|
||||||
kept = "corrupted"
|
kept = "corrupted"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cipher.verify(maccs)
|
cipher.verify(maccs)
|
||||||
except:
|
except:
|
||||||
|
@ -1074,11 +1113,10 @@ def work():
|
||||||
|
|
||||||
# Secure wipe not enabled
|
# Secure wipe not enabled
|
||||||
else:
|
else:
|
||||||
|
# Remove temporary zip file if created
|
||||||
if allFiles or onlyFiles:
|
if allFiles or onlyFiles:
|
||||||
# Remove temporary zip file if created
|
|
||||||
remove(inputFile)
|
remove(inputFile)
|
||||||
|
|
||||||
print(kept,reedsoloFixed)
|
|
||||||
# Show appropriate notice if file corrupted or modified
|
# Show appropriate notice if file corrupted or modified
|
||||||
if not kept:
|
if not kept:
|
||||||
statusString.set(f"Completed. (Click here to show output 🡪)")
|
statusString.set(f"Completed. (Click here to show output 🡪)")
|
||||||
|
@ -1098,10 +1136,10 @@ def work():
|
||||||
|
|
||||||
status.config(cursor="hand2")
|
status.config(cursor="hand2")
|
||||||
|
|
||||||
# A little hack since strings are immutable
|
# A little hack to prevent reference nonsense
|
||||||
output = "".join([i for i in outputFile])
|
output = "".join([i for i in outputFile])
|
||||||
|
|
||||||
# Bind the output file
|
# Bind the output file to the status label
|
||||||
if platform.system()=="Windows":
|
if platform.system()=="Windows":
|
||||||
status.bind("<Button-1>",lambda e:showOutput(output.replace("/","\\")))
|
status.bind("<Button-1>",lambda e:showOutput(output.replace("/","\\")))
|
||||||
else:
|
else:
|
||||||
|
@ -1116,6 +1154,7 @@ def work():
|
||||||
onlyFiles = []
|
onlyFiles = []
|
||||||
working = False
|
working = False
|
||||||
|
|
||||||
|
# Wraps the work() function and prevents overwrites
|
||||||
def wrapper(yes=False):
|
def wrapper(yes=False):
|
||||||
global working,mode,outputFile
|
global working,mode,outputFile
|
||||||
if mode=="encrypt":
|
if mode=="encrypt":
|
||||||
|
@ -1143,13 +1182,16 @@ def wrapper(yes=False):
|
||||||
working = False
|
working = False
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Update visuals with stats
|
||||||
def updateStats(total):
|
def updateStats(total):
|
||||||
global startTime,previousTime,done,stopUpdating,reedsolo,reedsoloFixed,reedsoloErrors,working
|
global startTime,previousTime,done,stopUpdating,reedsolo,reedsoloFixed,reedsoloErrors,working
|
||||||
while True:
|
while True:
|
||||||
validStatus = (
|
validStatus = (
|
||||||
statusString.get().startswith("Working") or statusString.get().startswith("Deriving")
|
statusString.get().startswith("Working") or statusString.get().startswith("Deriving")
|
||||||
)
|
)
|
||||||
|
# Make sure to stop updating if needed
|
||||||
if not stopUpdating and validStatus and working:
|
if not stopUpdating and validStatus and working:
|
||||||
|
# Some basic math to calculate speed, ETA, etc.
|
||||||
elapsed = (datetime.now()-previousTime).total_seconds() or 0.0001
|
elapsed = (datetime.now()-previousTime).total_seconds() or 0.0001
|
||||||
sinceStart = (datetime.now()-startTime).total_seconds() or 0.0001
|
sinceStart = (datetime.now()-startTime).total_seconds() or 0.0001
|
||||||
previousTime = datetime.now()
|
previousTime = datetime.now()
|
||||||
|
@ -1162,9 +1204,10 @@ def updateStats(total):
|
||||||
|
|
||||||
info = f"Working... {min(percent,100):.0f}% at {speed:.2f} MB/s (ETA: {eta})"
|
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:
|
if reedsolo and mode=="decrypt" and reedsoloFixed:
|
||||||
tmp = "s" if reedsoloFixed!=1 else ""
|
tmp = "s" if reedsoloFixed!=1 else ""
|
||||||
info += f", fixed {reedsoloFixed} error{tmp}"
|
info += f", fixed {reedsoloFixed} bytes{tmp}"
|
||||||
|
|
||||||
if reedsolo and mode=="decrypt" and reedsoloErrors:
|
if reedsolo and mode=="decrypt" and reedsoloErrors:
|
||||||
info += f", {reedsoloErrors} MB unrecoverable"
|
info += f", {reedsoloErrors} MB unrecoverable"
|
||||||
|
@ -1175,11 +1218,13 @@ def updateStats(total):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Securely wipe file(s) via system internals
|
||||||
def secureWipe(fin):
|
def secureWipe(fin):
|
||||||
statusString.set(strings[12])
|
statusString.set(strings[12])
|
||||||
|
|
||||||
# Check platform, erase accordingly
|
# Check platform, erase accordingly
|
||||||
if platform.system()=="Windows":
|
if platform.system()=="Windows":
|
||||||
|
# Recursively delete folders
|
||||||
if isdir(fin):
|
if isdir(fin):
|
||||||
paths = []
|
paths = []
|
||||||
for i in Path(fin).rglob("*"):
|
for i in Path(fin).rglob("*"):
|
||||||
|
@ -1195,11 +1240,16 @@ def secureWipe(fin):
|
||||||
statusString.set(strings[12])
|
statusString.set(strings[12])
|
||||||
progress["value"] = 100
|
progress["value"] = 100
|
||||||
system(f'sdelete64.exe "{fin}" -p 4 -nobanner')
|
system(f'sdelete64.exe "{fin}" -p 4 -nobanner')
|
||||||
|
|
||||||
|
# MacOS
|
||||||
elif platform.system()=="Darwin":
|
elif platform.system()=="Darwin":
|
||||||
system(f'rm -rfP "{fin}"')
|
system(f'rm -rfP "{fin}"')
|
||||||
|
|
||||||
|
# Linux
|
||||||
else:
|
else:
|
||||||
system(f'shred -uz "{fin}" -n 4')
|
system(f'shred -uz "{fin}" -n 4')
|
||||||
|
|
||||||
|
# Show output file in user's file manager
|
||||||
def showOutput(file):
|
def showOutput(file):
|
||||||
if platform.system()=="Windows":
|
if platform.system()=="Windows":
|
||||||
system(f'explorer /select,"{file}"')
|
system(f'explorer /select,"{file}"')
|
||||||
|
@ -1350,15 +1400,19 @@ def disableAllInputs():
|
||||||
keepBtn["state"] = "disabled"
|
keepBtn["state"] = "disabled"
|
||||||
rsBtn["state"] = "disabled"
|
rsBtn["state"] = "disabled"
|
||||||
|
|
||||||
|
# Allow window to close if not encrypting/decrypting
|
||||||
def onClose():
|
def onClose():
|
||||||
global working
|
global working
|
||||||
if not working:
|
if not working:
|
||||||
tk.destroy()
|
tk.destroy()
|
||||||
|
|
||||||
|
# Generate Reed-Solomon codecs
|
||||||
def prepare():
|
def prepare():
|
||||||
global rs13,rs128
|
global rs13,rs128
|
||||||
rs13 = RSCodec(13)
|
rs13 = RSCodec(13)
|
||||||
rs128 = RSCodec(128)
|
rs128 = RSCodec(128)
|
||||||
|
|
||||||
|
# Accept sdelete64's EULA
|
||||||
if platform.system()=="Windows":
|
if platform.system()=="Windows":
|
||||||
system("sdelete64.exe /accepteula")
|
system("sdelete64.exe /accepteula")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
@ -1366,8 +1420,9 @@ def prepare():
|
||||||
# Prepare Reed-Solomon codecs
|
# Prepare Reed-Solomon codecs
|
||||||
Thread(target=prepare,daemon=True).start()
|
Thread(target=prepare,daemon=True).start()
|
||||||
|
|
||||||
|
# Bind tkinter close event
|
||||||
tk.protocol("WM_DELETE_WINDOW",onClose)
|
tk.protocol("WM_DELETE_WINDOW",onClose)
|
||||||
|
|
||||||
# Start tkinter
|
# Start tkinter
|
||||||
tk.mainloop()
|
tk.mainloop()
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
Loading…
Reference in New Issue