Update v2.py
This commit is contained in:
parent
20dab969bf
commit
80a0572d87
|
@ -20,14 +20,14 @@ from monocypher import lock,unlock,wipe,Blake2b
|
||||||
from hmac import compare_digest
|
from hmac import compare_digest
|
||||||
from reedsolo import RSCodec,ReedSolomonError
|
from reedsolo import RSCodec,ReedSolomonError
|
||||||
from os import urandom,fsync,remove,system
|
from os import urandom,fsync,remove,system
|
||||||
from os.path import getsize,expanduser,isdir,exists
|
from os.path import getsize,expanduser,isdir,exists,dirname,abspath,realpath
|
||||||
from os.path import dirname,abspath,realpath
|
from os.path import join as pathJoin,split as pathSplit
|
||||||
from os.path import join as pathJoin
|
#from os.path import split as pathSplit
|
||||||
from os.path import split as pathSplit
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
from tkinterdnd2 import TkinterDnD,DND_FILES
|
from tkinterdnd2 import TkinterDnD,DND_FILES
|
||||||
from ttkthemes import ThemedStyle
|
from ttkthemes import ThemedStyle
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tkinter
|
import tkinter
|
||||||
import tkinter.ttk
|
import tkinter.ttk
|
||||||
|
@ -35,27 +35,18 @@ import tkinter.scrolledtext
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
# Disable high DPI to prevent layout shifts
|
|
||||||
try:
|
|
||||||
from ctypes import windll
|
|
||||||
windll.shcore.SetProcessDpiAwareness(0)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Global variables
|
# Global variables
|
||||||
rootDir = dirname(realpath(__file__))
|
rootDir = dirname(realpath(__file__))
|
||||||
working = False
|
working = False
|
||||||
mode = False
|
mode = False
|
||||||
inputFile = False
|
inputFile = False
|
||||||
outputFile = False
|
outputFile = False
|
||||||
outputPath = False
|
|
||||||
kept = False
|
kept = False
|
||||||
rs128 = False
|
rs128 = False
|
||||||
rs13 = False
|
rs13 = False
|
||||||
allFiles = False
|
allFiles = False
|
||||||
onlyFolders = False
|
onlyFolders = False
|
||||||
onlyFiles = False
|
onlyFiles = False
|
||||||
filesLoaded = False
|
|
||||||
|
|
||||||
# Strings
|
# Strings
|
||||||
strings = [
|
strings = [
|
||||||
|
@ -75,24 +66,57 @@ strings = [
|
||||||
"Output file already exists. Would you like to overwrite it?",
|
"Output file already exists. Would you like to overwrite it?",
|
||||||
"Exiting now will lead to broken output. Are you sure?",
|
"Exiting now will lead to broken output. Are you sure?",
|
||||||
"Prevent corruption using Reed-Solomon",
|
"Prevent corruption using Reed-Solomon",
|
||||||
"Creating Reed-Solomon tables...",
|
"",
|
||||||
"Unknown error occured. Please try again."
|
"Unknown error occured. Please try again.",
|
||||||
|
"Drag and drop file(s) and folder(s) into this window.",
|
||||||
|
"File metadata (read-only):",
|
||||||
|
"Error. The input file couldn't be decoded as UTF-8."
|
||||||
]
|
]
|
||||||
|
|
||||||
# Create root tk with Drag and Drop support
|
# Create root tk
|
||||||
from hdpitkinter import HdpiTk
|
tk = TkinterDnD.Tk()
|
||||||
tk = TkinterDnD.Tk()#HdpiTk()#TkinterDnD.Tk()
|
tk.geometry("480x512")
|
||||||
tk.geometry("480x520")
|
|
||||||
tk.title("Picocrypt")
|
tk.title("Picocrypt")
|
||||||
tk.resizable(0,0)
|
tk.resizable(0,0)
|
||||||
tk.configure(background="#ffffff")
|
tk.configure(background="#f5f6f7")
|
||||||
ThemedStyle(tk).set_theme("arc")
|
ThemedStyle(tk).set_theme("arc")
|
||||||
|
|
||||||
|
# Enable high DPI on Windows
|
||||||
|
def Get_HWND_DPI(window_handle):
|
||||||
|
from ctypes import windll,pointer,wintypes
|
||||||
|
windll.shcore.SetProcessDpiAwareness(1)
|
||||||
|
DPI100pc = 96
|
||||||
|
DPI_type = 0
|
||||||
|
winH = wintypes.HWND(window_handle)
|
||||||
|
monitorhandle = windll.user32.MonitorFromWindow(
|
||||||
|
winH,wintypes.DWORD(2)
|
||||||
|
)
|
||||||
|
X = wintypes.UINT()
|
||||||
|
Y = wintypes.UINT()
|
||||||
|
try:
|
||||||
|
windll.shcore.GetDpiForMonitor(
|
||||||
|
monitorhandle,DPI_type,pointer(X),pointer(Y)
|
||||||
|
)
|
||||||
|
return X.value,Y.value,(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":
|
||||||
|
MakeTkDPIAware(tk)
|
||||||
|
|
||||||
# Add some styling
|
# Add some styling
|
||||||
style = tkinter.ttk.Style()
|
style = tkinter.ttk.Style()
|
||||||
style.configure("TCheckbutton",background="#ffffff")
|
|
||||||
style.configure("TLabel",background="#ffffff")
|
|
||||||
style.configure("TSeparator",background="#ffffff")
|
|
||||||
|
|
||||||
# Try setting window icon if it exists
|
# Try setting window icon if it exists
|
||||||
try:
|
try:
|
||||||
|
@ -101,9 +125,13 @@ try:
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Dummy button used for removing ugly highlights
|
||||||
|
dummy = tkinter.ttk.Button(tk)
|
||||||
|
dummy.place(x=480,y=0)
|
||||||
|
|
||||||
# Label that shows the input files
|
# Label that shows the input files
|
||||||
inputString = tkinter.StringVar(tk)
|
inputString = tkinter.StringVar(tk)
|
||||||
inputString.set("Drag and drop file(s) and folder(s) into this window.")
|
inputString.set(strings[18])
|
||||||
inputLabel = tkinter.ttk.Label(
|
inputLabel = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=inputString
|
textvariable=inputString
|
||||||
|
@ -114,12 +142,13 @@ inputLabel.place(x=20,y=18)
|
||||||
clearInput = tkinter.ttk.Button(
|
clearInput = tkinter.ttk.Button(
|
||||||
tk,
|
tk,
|
||||||
text="Clear",
|
text="Clear",
|
||||||
cursor="hand2"
|
command = lambda:resetUI()
|
||||||
)
|
)
|
||||||
clearInput.place(x=400,y=12,width=60,height=28)
|
clearInput.place(x=400,y=12,width=60,height=28)
|
||||||
|
clearInput["state"] = "disabled"
|
||||||
|
|
||||||
# Separator for aesthetics
|
# Separator for aesthetics
|
||||||
separator = tkinter.Frame(#tkinter.ttk.Separator(tk)
|
separator = tkinter.Frame(
|
||||||
tk,
|
tk,
|
||||||
bg="#dfe3ed",
|
bg="#dfe3ed",
|
||||||
height=1
|
height=1
|
||||||
|
@ -149,7 +178,7 @@ pcvLabel.place(x=434,y=68)
|
||||||
# A frame to allow output box to fill width
|
# A frame to allow output box to fill width
|
||||||
outputFrame = tkinter.Frame(
|
outputFrame = tkinter.Frame(
|
||||||
tk,
|
tk,
|
||||||
width=410,
|
width=440,
|
||||||
height=24
|
height=24
|
||||||
)
|
)
|
||||||
outputFrame.place(x=20,y=66)
|
outputFrame.place(x=20,y=66)
|
||||||
|
@ -168,7 +197,7 @@ passwordLabel = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=passwordString
|
textvariable=passwordString
|
||||||
)
|
)
|
||||||
passwordLabel.place(x=20,y=98)
|
passwordLabel.place(x=20,y=100)
|
||||||
passwordLabel["state"] = "disabled"
|
passwordLabel["state"] = "disabled"
|
||||||
|
|
||||||
# Allow password input to fill width
|
# Allow password input to fill width
|
||||||
|
@ -196,7 +225,7 @@ cPasswordLabel = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=cPasswordString
|
textvariable=cPasswordString
|
||||||
)
|
)
|
||||||
cPasswordLabel.place(x=20,y=148)
|
cPasswordLabel.place(x=20,y=150)
|
||||||
cPasswordLabel["state"] = "disabled"
|
cPasswordLabel["state"] = "disabled"
|
||||||
|
|
||||||
# Allow confirm password input to fill width
|
# Allow confirm password input to fill width
|
||||||
|
@ -224,7 +253,7 @@ metadataLabel = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=metadataString
|
textvariable=metadataString
|
||||||
)
|
)
|
||||||
metadataLabel.place(x=20,y=200)
|
metadataLabel.place(x=20,y=202)
|
||||||
metadataLabel["state"] = "disabled"
|
metadataLabel["state"] = "disabled"
|
||||||
|
|
||||||
# Frame so metadata box can fill width
|
# Frame so metadata box can fill width
|
||||||
|
@ -268,7 +297,7 @@ keepBtn = tkinter.ttk.Checkbutton(
|
||||||
onvalue=1,
|
onvalue=1,
|
||||||
offvalue=0
|
offvalue=0
|
||||||
)
|
)
|
||||||
keepBtn.place(x=18,y=332)
|
keepBtn.place(x=18,y=329)
|
||||||
keepBtn["state"] = "disabled"
|
keepBtn["state"] = "disabled"
|
||||||
|
|
||||||
|
|
||||||
|
@ -276,24 +305,24 @@ keepBtn["state"] = "disabled"
|
||||||
erase = tkinter.IntVar(tk)
|
erase = tkinter.IntVar(tk)
|
||||||
eraseBtn = tkinter.ttk.Checkbutton(
|
eraseBtn = tkinter.ttk.Checkbutton(
|
||||||
tk,
|
tk,
|
||||||
text=strings[12],
|
text=strings[11],
|
||||||
variable=erase,
|
variable=erase,
|
||||||
onvalue=1,
|
onvalue=1,
|
||||||
offvalue=0
|
offvalue=0
|
||||||
)
|
)
|
||||||
eraseBtn.place(x=18,y=352)
|
eraseBtn.place(x=18,y=349)
|
||||||
eraseBtn["state"] = "disabled"
|
eraseBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Check box for enabling Reed-Solomon anti-corruption
|
# Check box for enabling Reed-Solomon anti-corruption
|
||||||
rs = tkinter.IntVar(tk)
|
rs = tkinter.IntVar(tk)
|
||||||
rsBtn = tkinter.ttk.Checkbutton(
|
rsBtn = tkinter.ttk.Checkbutton(
|
||||||
tk,
|
tk,
|
||||||
text=strings[16],
|
text=strings[15],
|
||||||
variable=rs,
|
variable=rs,
|
||||||
onvalue=1,
|
onvalue=1,
|
||||||
offvalue=0
|
offvalue=0
|
||||||
)
|
)
|
||||||
rsBtn.place(x=18,y=372)
|
rsBtn.place(x=18,y=369)
|
||||||
rsBtn["state"] = "disabled"
|
rsBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Frame so start and cancel button can fill width
|
# Frame so start and cancel button can fill width
|
||||||
|
@ -310,10 +339,11 @@ startFrame.config(background="#ffffff")
|
||||||
# Start button
|
# Start button
|
||||||
startBtn = tkinter.ttk.Button(
|
startBtn = tkinter.ttk.Button(
|
||||||
startFrame,
|
startFrame,
|
||||||
text="Start"
|
text="Start",
|
||||||
|
command=lambda:Thread(target=work,daemon=True).start()
|
||||||
)
|
)
|
||||||
startBtn.grid(row=0,column=0,stick="nesw")
|
startBtn.grid(row=0,column=0,stick="nesw")
|
||||||
#startBtn["state"] = "disabled"
|
startBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Cancel button
|
# Cancel button
|
||||||
cancelBtn = tkinter.ttk.Button(
|
cancelBtn = tkinter.ttk.Button(
|
||||||
|
@ -322,7 +352,7 @@ cancelBtn = tkinter.ttk.Button(
|
||||||
)
|
)
|
||||||
cancelBtn.grid(stick="nesw")
|
cancelBtn.grid(stick="nesw")
|
||||||
cancelBtn.grid(row=0,column=1)
|
cancelBtn.grid(row=0,column=1)
|
||||||
#cancelBtn["state"] = "disabled"
|
cancelBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Progress bar
|
# Progress bar
|
||||||
progress = tkinter.ttk.Progressbar(
|
progress = tkinter.ttk.Progressbar(
|
||||||
|
@ -340,8 +370,7 @@ status = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=statusString
|
textvariable=statusString
|
||||||
)
|
)
|
||||||
status.place(x=20,y=456)
|
status.place(x=20,y=450)
|
||||||
status["state"] = "disabled"
|
|
||||||
|
|
||||||
# Credits :)
|
# Credits :)
|
||||||
hint = "Created by Evan Su. Click for details and source."
|
hint = "Created by Evan Su. Click for details and source."
|
||||||
|
@ -352,7 +381,7 @@ credits = tkinter.ttk.Label(
|
||||||
textvariable=creditsString,
|
textvariable=creditsString,
|
||||||
cursor="hand2"
|
cursor="hand2"
|
||||||
)
|
)
|
||||||
credits.place(x=20,y=488)
|
credits.place(x=20,y=480)
|
||||||
source = "https://github.com/HACKERALERT/Picocrypt"
|
source = "https://github.com/HACKERALERT/Picocrypt"
|
||||||
credits.bind("<Button-1>",lambda e:webbrowser.open(source))
|
credits.bind("<Button-1>",lambda e:webbrowser.open(source))
|
||||||
credits["state"] = "disabled"
|
credits["state"] = "disabled"
|
||||||
|
@ -365,8 +394,591 @@ version = tkinter.ttk.Label(
|
||||||
textvariable=versionString
|
textvariable=versionString
|
||||||
)
|
)
|
||||||
version["state"] = "disabled"
|
version["state"] = "disabled"
|
||||||
version.place(x=435,y=488)
|
version.place(x=435,y=480)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
if __name__=="__main__":
|
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
|
||||||
|
metadataString.set("File metadata (read only):")
|
||||||
|
metadataInput["state"] = "normal"
|
||||||
|
metadataInput.delete("1.0",tkinter.END)
|
||||||
|
metadataInput.insert("1.0","hi")
|
||||||
|
metadataInput["state"] = "disabled"
|
||||||
|
|
||||||
|
# Insert filename into output box
|
||||||
|
outputFrame.config(width=440)
|
||||||
|
outputInput["state"] = "normal"
|
||||||
|
outputInput.delete(0,tkinter.END)
|
||||||
|
outputInput.insert(0,inputFile[:-4])
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
setDecryptionUI()
|
||||||
|
else:
|
||||||
|
mode = "encrypt"
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
setEncryptionUI()
|
||||||
|
|
||||||
|
# Update output box with appropriate name
|
||||||
|
if inputFile:
|
||||||
|
outputInput.insert(0,inputFile)
|
||||||
|
else:
|
||||||
|
if onlyFiles:
|
||||||
|
tmp = Path(onlyFiles[0]).parent.absolute()
|
||||||
|
else:
|
||||||
|
tmp = Path(onlyFolders[0]).parent.absolute()
|
||||||
|
tmp = pathJoin(tmp,"Encrypted.zip")
|
||||||
|
tmp = tmp.replace("\\","/")
|
||||||
|
outputInput.insert(0,tmp)
|
||||||
|
suffix = " (will encrypt)"
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# UTF-8 decode error
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
statusString.set(strings[20])
|
||||||
|
progress["value"] = 100
|
||||||
|
|
||||||
|
# Nothing happened, reset UI
|
||||||
|
except:
|
||||||
|
resetUI()
|
||||||
|
|
||||||
|
# Bind drag and drop to window
|
||||||
|
def onDrop(e):
|
||||||
|
global working
|
||||||
|
if not working:
|
||||||
|
filesDragged(e.data)
|
||||||
|
clearInput["state"] = "normal"
|
||||||
|
clearInput.config(cursor="hand2")
|
||||||
|
tk.drop_target_register(DND_FILES)
|
||||||
|
tk.dnd_bind("<<Drop>>",onDrop)
|
||||||
|
|
||||||
|
def work():
|
||||||
|
global inputFile,outputFile,working,mode,rs13,rs128,onlyFiles,onlyFolders,allFiles
|
||||||
|
disableAllInputs()
|
||||||
|
dummy.focus()
|
||||||
|
|
||||||
|
# Set and get some variables
|
||||||
|
working = True
|
||||||
|
headerBroken = False
|
||||||
|
password = passwordInput.get().encode("utf-8")
|
||||||
|
metadata = metadataInput.get("1.0",tkinter.END).encode("utf-8")
|
||||||
|
|
||||||
|
# Decide if encrypting or decrypting
|
||||||
|
if mode=="encrypt":
|
||||||
|
outputFile = outputInput.get()+".pcv"
|
||||||
|
else:
|
||||||
|
outputFile = outputInput.get()
|
||||||
|
|
||||||
|
# Make sure passwords match
|
||||||
|
if passwordInput.get()!=cPasswordInput.get() and mode=="encrypt":
|
||||||
|
setEncryptionUI()
|
||||||
|
statusString.set("Passwords don't match.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 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("Error. Folder is empty.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode=="encrypt":
|
||||||
|
salt = urandom(16)
|
||||||
|
fout = open(outputFile,"wb+")
|
||||||
|
#print(rs128.encode(b"-"))
|
||||||
|
if rs.get()==1:
|
||||||
|
reedsolo = True
|
||||||
|
fout.write(rs128.encode(b"+"))
|
||||||
|
else:
|
||||||
|
reedsolo = False
|
||||||
|
fout.write(rs128.encode(b"-"))
|
||||||
|
|
||||||
|
metadata = rs128.encode(metadata)
|
||||||
|
tmp = len(metadata)
|
||||||
|
tmp = f"{tmp:+<10}"
|
||||||
|
tmp = rs128.encode(tmp.encode("utf-8"))
|
||||||
|
|
||||||
|
#print(tmp)
|
||||||
|
#print(metadata)
|
||||||
|
#print(rs128.encode(salt))
|
||||||
|
fout.write(tmp)
|
||||||
|
fout.write(metadata)
|
||||||
|
fout.write(rs128.encode(salt)) # Argon2 salt
|
||||||
|
fout.write(b"0"*192) # BLAKE2 of key
|
||||||
|
fout.write(b"0"*192) # BLAKE2 MAC
|
||||||
|
fout.write(b"0"*192) # BLAKE2 CRC
|
||||||
|
else:
|
||||||
|
tmp = fin.read(129)
|
||||||
|
#print(tmp)
|
||||||
|
if bytes(rs128.decode(tmp)[0])==b"+":
|
||||||
|
reedsolo = True
|
||||||
|
else:
|
||||||
|
reedsolo = False
|
||||||
|
metadataLength = fin.read(138)
|
||||||
|
#print(metadataLength)
|
||||||
|
metadataLength = bytes(rs128.decode(metadataLength)[0])
|
||||||
|
metadataLength = metadataLength.replace(b"+",b"")
|
||||||
|
#print(metadataLength)
|
||||||
|
fin.read(int(metadataLength.decode("utf-8")))
|
||||||
|
salt = fin.read(144)
|
||||||
|
#print(salt)
|
||||||
|
keycs = fin.read(192)
|
||||||
|
#print(keycs)
|
||||||
|
maccs = fin.read(192)
|
||||||
|
crccs = fin.read(192)
|
||||||
|
|
||||||
|
salt = bytes(rs128.decode(salt)[0])
|
||||||
|
keycs = bytes(rs128.decode(keycs)[0])
|
||||||
|
maccs = bytes(rs128.decode(maccs)[0])
|
||||||
|
crccs = bytes(rs128.decode(crccs)[0])
|
||||||
|
|
||||||
|
statusString.set(strings[9])
|
||||||
|
|
||||||
|
key = hash_secret_raw(
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
time_cost=8,
|
||||||
|
memory_cost=2**20,
|
||||||
|
parallelism=8,
|
||||||
|
hash_len=32,
|
||||||
|
type=argonType.D
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.stop()
|
||||||
|
progress.config(mode="determinate")
|
||||||
|
progress["value"] = 0
|
||||||
|
|
||||||
|
check = Blake2b(hash_size=64)
|
||||||
|
check.update(key)
|
||||||
|
check = check.finalize()
|
||||||
|
|
||||||
|
if mode=="decrypt":
|
||||||
|
if not compare_digest(check,keycs):
|
||||||
|
if not headerBroken:
|
||||||
|
statusString.set(strings[2])
|
||||||
|
fin.close()
|
||||||
|
setDecryptionUI()
|
||||||
|
return
|
||||||
|
fout = open(outputFile,"wb+")
|
||||||
|
|
||||||
|
crc = Blake2b(hash_size=64)
|
||||||
|
subKey = Blake2b(hash_size=32)
|
||||||
|
subKey.update(key)
|
||||||
|
subKey = subKey.finalize()
|
||||||
|
mac = Blake2b(key=subKey,hash_size=64)
|
||||||
|
|
||||||
|
done = 0
|
||||||
|
total = getsize(inputFile)
|
||||||
|
|
||||||
|
startTime = datetime.now()
|
||||||
|
previousTime = datetime.now()
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
while True:
|
||||||
|
if mode=="encrypt":
|
||||||
|
piece = fin.read(2**25)
|
||||||
|
if not piece:
|
||||||
|
break
|
||||||
|
tag = f"{counter:+>32}".encode("utf-8")
|
||||||
|
piece += tag
|
||||||
|
else:
|
||||||
|
piece = fin.read(2**25+200)
|
||||||
|
if not piece:
|
||||||
|
break
|
||||||
|
if mode=="encrypt":
|
||||||
|
nonce = urandom(24)
|
||||||
|
digest,data = lock(key,nonce,piece)
|
||||||
|
crc.update(data)
|
||||||
|
mac.update(data)
|
||||||
|
fout.write(nonce)
|
||||||
|
fout.write(digest)
|
||||||
|
fout.write(data)
|
||||||
|
#print(nonce)
|
||||||
|
#print("=====")
|
||||||
|
#print(digest)
|
||||||
|
#print("=======")
|
||||||
|
#print(data)
|
||||||
|
else:
|
||||||
|
nonce = piece[:24]
|
||||||
|
digest = piece[24:40]
|
||||||
|
piece = piece[40:]
|
||||||
|
#print(nonce)
|
||||||
|
#print("=====")
|
||||||
|
#print(digest)
|
||||||
|
#print("=======")
|
||||||
|
#print(piece)
|
||||||
|
crc.update(piece)
|
||||||
|
mac.update(piece)
|
||||||
|
data = unlock(key,nonce,digest,piece)
|
||||||
|
if int(data[-32:].replace(b"+",b"").decode("utf-8"))!=counter:
|
||||||
|
print("Counter failed")
|
||||||
|
fout.write(data[:-32])
|
||||||
|
|
||||||
|
counter += 1
|
||||||
|
elapsed = (datetime.now()-previousTime).total_seconds() or 0.0001
|
||||||
|
sinceStart = (datetime.now()-startTime).total_seconds() or 0.0001
|
||||||
|
previousTime = datetime.now()
|
||||||
|
|
||||||
|
percent = done*100/total
|
||||||
|
progress["value"] = percent
|
||||||
|
|
||||||
|
speed = (done/sinceStart)/10**6 or 0.0001
|
||||||
|
eta = round((total-done)/(speed*10**6))
|
||||||
|
|
||||||
|
info = f"{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 += 2**25
|
||||||
|
|
||||||
|
if mode=="encrypt":
|
||||||
|
fout.flush()
|
||||||
|
fout.close()
|
||||||
|
fout = open(outputFile,"r+b")
|
||||||
|
fout.seek(129+138+len(metadata)+144)
|
||||||
|
fout.write(rs128.encode(check))
|
||||||
|
fout.write(rs128.encode(mac.finalize()))
|
||||||
|
fout.write(rs128.encode(crc.finalize()))
|
||||||
|
else:
|
||||||
|
if not compare_digest(crccs,crc.finalize()):
|
||||||
|
statusString.set(strings[3])
|
||||||
|
progress["value"] = 100
|
||||||
|
fin.close()
|
||||||
|
fout.close()
|
||||||
|
|
||||||
|
if keep.get()!=1:
|
||||||
|
remove(outputFile)
|
||||||
|
setDecryptionUI()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if not kept:
|
||||||
|
kept = "corrupted"
|
||||||
|
|
||||||
|
if not compare_digest(maccs,mac.finalize()):
|
||||||
|
#if not reedsoloErrorCount and not headerBroken:
|
||||||
|
if True:
|
||||||
|
# File is modified
|
||||||
|
statusString.set(modifiedNotice)
|
||||||
|
progress["value"] = 100
|
||||||
|
fin.close()
|
||||||
|
fout.close()
|
||||||
|
# If keep file not checked...
|
||||||
|
if keep.get()!=1:
|
||||||
|
remove(outputFile)
|
||||||
|
# Reset UI
|
||||||
|
setDecryptionUI()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if not kept:
|
||||||
|
kept = "modified"
|
||||||
|
|
||||||
|
# Flush outputs, close files
|
||||||
|
#if not kept:
|
||||||
|
if True:
|
||||||
|
fout.flush()
|
||||||
|
fsync(fout.fileno())
|
||||||
|
fout.close()
|
||||||
|
fin.close()
|
||||||
|
|
||||||
|
print("DONEDONEDONEDONEDONEDONEDONEDONEDONEDONE")
|
||||||
|
# Securely wipe files as necessary
|
||||||
|
if wipe:
|
||||||
|
if onlyFolders:
|
||||||
|
for i in onlyFolders:
|
||||||
|
secureWipe(i)
|
||||||
|
if onlyFiles:
|
||||||
|
for i in range(len(onlyFiles)):
|
||||||
|
statusString.set(
|
||||||
|
strings[12]+f" ({i}/{len(onlyFiles)}"
|
||||||
|
)
|
||||||
|
progress["value"] = i/len(onlyFiles)
|
||||||
|
secureWipe(onlyFiles[i])
|
||||||
|
secureWipe(inputFile)
|
||||||
|
# Secure wipe not enabled
|
||||||
|
else:
|
||||||
|
if allFiles or onlyFiles:
|
||||||
|
# Remove temporary zip file if created
|
||||||
|
remove(inputFile)
|
||||||
|
|
||||||
|
# Show appropriate notice if file corrupted or modified
|
||||||
|
#if not kept:
|
||||||
|
if True:
|
||||||
|
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()
|
||||||
|
inputFile = ""
|
||||||
|
outputFile = ""
|
||||||
|
password = ""
|
||||||
|
metadata = ""
|
||||||
|
kept = False
|
||||||
|
working = False
|
||||||
|
allFiles = False
|
||||||
|
onlyFolders = False
|
||||||
|
onlyFiles = []
|
||||||
|
|
||||||
|
# Wipe keys for safety
|
||||||
|
#del fin,fout,cipher,key
|
||||||
|
|
||||||
|
def secureWipe(fin):
|
||||||
|
statusString.set(strings[12])
|
||||||
|
# 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(strings[12]+f" ({i}/{len(paths)})")
|
||||||
|
progress["value"] = 100*i/len(paths)
|
||||||
|
system(f'cd "{paths[i]}" && "{rootDir}/sdelete64.exe" * -p 4 -s -nobanner')
|
||||||
|
system(f'cd "{rootDir}"')
|
||||||
|
rmtree(fin)
|
||||||
|
else:
|
||||||
|
statusString.set(strings[12])
|
||||||
|
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')
|
||||||
|
|
||||||
|
# Reset UI to state where no files are selected
|
||||||
|
def resetUI():
|
||||||
|
inputString.set(strings[18])
|
||||||
|
inputLabel["state"] = "normal"
|
||||||
|
clearInput["state"] = "disabled"
|
||||||
|
clearInput.config(cursor="")
|
||||||
|
outputLabel["state"] = "disabled"
|
||||||
|
outputFrame.config(width=440)
|
||||||
|
outputInput["state"] = "normal"
|
||||||
|
outputInput.delete(0,"end")
|
||||||
|
outputInput["state"] = "disabled"
|
||||||
|
passwordLabel["state"] = "disabled"
|
||||||
|
passwordInput["state"] = "normal"
|
||||||
|
passwordInput.delete(0,"end")
|
||||||
|
passwordInput["state"] = "disabled"
|
||||||
|
cPasswordString.set("Confirm password:")
|
||||||
|
cPasswordLabel["state"] = "disabled"
|
||||||
|
cPasswordInput["state"] = "normal"
|
||||||
|
cPasswordInput.delete(0,"end")
|
||||||
|
cPasswordInput["state"] = "disabled"
|
||||||
|
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"
|
||||||
|
startBtn["state"] = "disabled"
|
||||||
|
cancelBtn["state"] = "disabled"
|
||||||
|
progress.stop()
|
||||||
|
progress.config(mode="determinate")
|
||||||
|
progress["value"] = 0
|
||||||
|
dummy.focus()
|
||||||
|
|
||||||
|
# Set UI to encryption state
|
||||||
|
def setEncryptionUI():
|
||||||
|
outputLabel["state"] = "normal"
|
||||||
|
outputInput["state"] = "normal"
|
||||||
|
outputFrame.config(width=410)
|
||||||
|
passwordLabel["state"] = "normal"
|
||||||
|
passwordInput["state"] = "normal"
|
||||||
|
cPasswordLabel["state"] = "normal"
|
||||||
|
cPasswordInput["state"] = "normal"
|
||||||
|
metadataLabel["state"] = "normal"
|
||||||
|
metadataInput["state"] = "normal"
|
||||||
|
eraseBtn["state"] = "normal"
|
||||||
|
rsBtn["state"] = "normal"
|
||||||
|
startBtn["state"] = "normal"
|
||||||
|
progress.stop()
|
||||||
|
progress.config(mode="determinate")
|
||||||
|
progress["value"] = 0
|
||||||
|
|
||||||
|
# Set UI to decryption state
|
||||||
|
def setDecryptionUI():
|
||||||
|
outputLabel["state"] = "normal"
|
||||||
|
outputInput["state"] = "normal"
|
||||||
|
outputFrame.config(width=440)
|
||||||
|
passwordLabel["state"] = "normal"
|
||||||
|
passwordInput["state"] = "normal"
|
||||||
|
metadataString.set(strings[19])
|
||||||
|
metadataInput["state"] = "disabled"
|
||||||
|
keepBtn["state"] = "normal"
|
||||||
|
startBtn["state"] = "normal"
|
||||||
|
progress.stop()
|
||||||
|
progress.config(mode="determinate")
|
||||||
|
progress["value"] = 0
|
||||||
|
|
||||||
|
# Disable all inputs while encrypting/decrypting
|
||||||
|
def disableAllInputs():
|
||||||
|
clearInput["state"] = "disabled"
|
||||||
|
outputInput["state"] = "disabled"
|
||||||
|
passwordInput["state"] = "disabled"
|
||||||
|
cPasswordInput["state"] = "disabled"
|
||||||
|
metadataInput["state"] = "disabled"
|
||||||
|
startBtn["state"] = "disabled"
|
||||||
|
eraseBtn["state"] = "disabled"
|
||||||
|
keepBtn["state"] = "disabled"
|
||||||
|
rsBtn["state"] = "disabled"
|
||||||
|
|
||||||
|
def prepareRsc():
|
||||||
|
global rs13,rs128
|
||||||
|
rs13 = RSCodec(13)
|
||||||
|
rs128 = RSCodec(128)
|
||||||
|
|
||||||
|
# Prepare Reed-Solomon codecs
|
||||||
|
Thread(target=prepareRsc,daemon=True).start()
|
||||||
|
|
||||||
|
# Start tkinter
|
||||||
tk.mainloop()
|
tk.mainloop()
|
||||||
|
|
Loading…
Reference in New Issue