v1.8 New Reed-Solomon feature
This commit is contained in:
parent
3184895f77
commit
7559754f2d
202
src/Picocrypt.py
202
src/Picocrypt.py
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Dependencies: argon2-cffi, pycryptodome
|
# Dependencies: argon2-cffi, pycryptodome, reedsolo
|
||||||
# Copyright (c) Evan Su (https://evansu.cc)
|
# Copyright (c) Evan Su (https://evansu.cc)
|
||||||
# Released under a GNU GPL v3 license
|
# Released under a GNU GPL v3 license
|
||||||
# https://github.com/HACKERALERT/Picocrypt
|
# https://github.com/HACKERALERT/Picocrypt
|
||||||
|
@ -9,12 +9,22 @@
|
||||||
try:
|
try:
|
||||||
from argon2.low_level import hash_secret_raw
|
from argon2.low_level import hash_secret_raw
|
||||||
from Crypto.Cipher import ChaCha20_Poly1305
|
from Crypto.Cipher import ChaCha20_Poly1305
|
||||||
|
try:
|
||||||
|
from creedsolo import ReedSolomonError
|
||||||
|
except:
|
||||||
|
from reedsolo import ReedSolomonError
|
||||||
except:
|
except:
|
||||||
# Libraries missing, install them
|
# Libraries missing, install them
|
||||||
from os import system
|
from os import system
|
||||||
system("sudo apt-get install python3-tk")
|
try:
|
||||||
system("python3 -m pip install argon2-cffi")
|
# Debian/Ubuntu based
|
||||||
system("python3 -m pip install pycryptodome")
|
system("sudo apt-get install python3-tk")
|
||||||
|
except:
|
||||||
|
# Fedora
|
||||||
|
system("sudo dnf install python3-tkinter")
|
||||||
|
system("python3 -m pip install argon2-cffi --no-cache-dir")
|
||||||
|
system("python3 -m pip install pycryptodome --no-cache-dir")
|
||||||
|
system("python3 -m pip install reedsolo --no-cache-dir")
|
||||||
|
|
||||||
# Imports
|
# Imports
|
||||||
from tkinter import filedialog,messagebox
|
from tkinter import filedialog,messagebox
|
||||||
|
@ -31,6 +41,10 @@ import tkinter
|
||||||
import tkinter.ttk
|
import tkinter.ttk
|
||||||
import tkinter.scrolledtext
|
import tkinter.scrolledtext
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
try:
|
||||||
|
from creedsolo import RSCodec,ReedSolomonError
|
||||||
|
except:
|
||||||
|
from reedsolo import RSCodec,ReedSolomonError
|
||||||
|
|
||||||
# Tk/Tcl is a little barbaric, disable
|
# Tk/Tcl is a little barbaric, disable
|
||||||
# high DPI so it doesn't look really ugly
|
# high DPI so it doesn't look really ugly
|
||||||
|
@ -57,11 +71,13 @@ derivingNotice = "Deriving key (takes a few seconds)..."
|
||||||
keepNotice = "Keep decrypted output even if it's corrupted or modified"
|
keepNotice = "Keep decrypted output even if it's corrupted or modified"
|
||||||
eraseNotice = "Securely erase and delete original file"
|
eraseNotice = "Securely erase and delete original file"
|
||||||
overwriteNotice = "Output file already exists. Would you like to overwrite it?"
|
overwriteNotice = "Output file already exists. Would you like to overwrite it?"
|
||||||
|
rsNotice = "Prevent corruption using Reed-Solomon"
|
||||||
|
rscNotice = "Creating Reed-Solomon tables..."
|
||||||
unknownErrorNotice = "Unknown error occured. Please try again."
|
unknownErrorNotice = "Unknown error occured. Please try again."
|
||||||
|
|
||||||
# Create root Tk
|
# Create root Tk
|
||||||
tk = tkinter.Tk()
|
tk = tkinter.Tk()
|
||||||
tk.geometry("480x420")
|
tk.geometry("480x440")
|
||||||
tk.title("Picocrypt")
|
tk.title("Picocrypt")
|
||||||
tk.configure(background="#f5f6f7")
|
tk.configure(background="#f5f6f7")
|
||||||
tk.resizable(0,0)
|
tk.resizable(0,0)
|
||||||
|
@ -95,13 +111,14 @@ def inputSelected():
|
||||||
inputFile = tmp
|
inputFile = tmp
|
||||||
# Decide if encrypting or decrypting
|
# Decide if encrypting or decrypting
|
||||||
if ".pcf" in inputFile.split("/")[-1]:
|
if ".pcf" in inputFile.split("/")[-1]:
|
||||||
suffix = " (will be decrypted)"
|
suffix = " (will decrypt)"
|
||||||
fin = open(inputFile,"rb+")
|
fin = open(inputFile,"rb+")
|
||||||
# Read file metadata
|
# Read file metadata
|
||||||
adlen = b""
|
adlen = b""
|
||||||
while True:
|
while True:
|
||||||
letter = fin.read(1)
|
letter = fin.read(1)
|
||||||
adlen += letter
|
if letter!=b"+":
|
||||||
|
adlen += letter
|
||||||
if letter==b"|":
|
if letter==b"|":
|
||||||
adlen = adlen[:-1]
|
adlen = adlen[:-1]
|
||||||
break
|
break
|
||||||
|
@ -115,13 +132,15 @@ def inputSelected():
|
||||||
adLabelString.set("File metadata (read only):")
|
adLabelString.set("File metadata (read only):")
|
||||||
keepBtn["state"] = "normal"
|
keepBtn["state"] = "normal"
|
||||||
eraseBtn["state"] = "disabled"
|
eraseBtn["state"] = "disabled"
|
||||||
|
rsBtn["state"] = "disabled"
|
||||||
else:
|
else:
|
||||||
# Update the UI
|
# Update the UI
|
||||||
eraseBtn["state"] = "normal"
|
eraseBtn["state"] = "normal"
|
||||||
keepBtn["state"] = "disabled"
|
keepBtn["state"] = "disabled"
|
||||||
|
rsBtn["state"] = "normal"
|
||||||
adArea["state"] = "normal"
|
adArea["state"] = "normal"
|
||||||
adArea.delete("1.0",tkinter.END)
|
adArea.delete("1.0",tkinter.END)
|
||||||
suffix = " (will be encrypted)"
|
suffix = " (will encrypt)"
|
||||||
adLabelString.set(adString)
|
adLabelString.set(adString)
|
||||||
# Enable password box, etc.
|
# Enable password box, etc.
|
||||||
inputString.set(inputFile.split("/")[-1]+suffix)
|
inputString.set(inputFile.split("/")[-1]+suffix)
|
||||||
|
@ -190,13 +209,23 @@ passwordInput["state"] = "disabled"
|
||||||
# Start the encryption/decryption process
|
# Start the encryption/decryption process
|
||||||
def start():
|
def start():
|
||||||
global inputFile,outputFile,password,ad,kept,working
|
global inputFile,outputFile,password,ad,kept,working
|
||||||
|
dummy.focus()
|
||||||
|
reedsolo = False
|
||||||
|
chunkSize = 2**20
|
||||||
|
|
||||||
# Decide if encrypting or decrypting
|
# Decide if encrypting or decrypting
|
||||||
if ".pcf" not in inputFile:
|
if ".pcf" not in inputFile:
|
||||||
mode = "encrypt"
|
mode = "encrypt"
|
||||||
outputFile = inputFile+".pcf"
|
outputFile = inputFile+".pcf"
|
||||||
|
reedsolo = rs.get()==1
|
||||||
else:
|
else:
|
||||||
mode = "decrypt"
|
mode = "decrypt"
|
||||||
|
test = open(inputFile,"rb+")
|
||||||
|
decider = test.read(1).decode("utf-8")
|
||||||
|
test.close()
|
||||||
|
if decider=="+":
|
||||||
|
reedsolo = True
|
||||||
|
print("reed solo")
|
||||||
outputFile = inputFile[:-4]
|
outputFile = inputFile[:-4]
|
||||||
|
|
||||||
# Check if file already exists
|
# Check if file already exists
|
||||||
|
@ -209,6 +238,17 @@ def start():
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Set progress bar indeterminate
|
||||||
|
progress.config(mode="indeterminate")
|
||||||
|
progress.start(15)
|
||||||
|
|
||||||
|
# Create Reed-Solomon object
|
||||||
|
if reedsolo:
|
||||||
|
statusString.set(rscNotice)
|
||||||
|
rsc = RSCodec(8)
|
||||||
|
reedsoloFixedCount = 0
|
||||||
|
reedsoloErrorCount = 0
|
||||||
|
|
||||||
# Set and get some variables
|
# Set and get some variables
|
||||||
working = True
|
working = True
|
||||||
dummy.focus()
|
dummy.focus()
|
||||||
|
@ -220,10 +260,17 @@ def start():
|
||||||
passwordInput["state"] = "disabled"
|
passwordInput["state"] = "disabled"
|
||||||
adArea["state"] = "disabled"
|
adArea["state"] = "disabled"
|
||||||
startBtn["state"] = "disabled"
|
startBtn["state"] = "disabled"
|
||||||
|
eraseBtn["state"] = "disabled"
|
||||||
keepBtn["state"] = "disabled"
|
keepBtn["state"] = "disabled"
|
||||||
|
rsBtn["state"] = "disabled"
|
||||||
|
|
||||||
fin = open(inputFile,"rb+")
|
fin = open(inputFile,"rb+")
|
||||||
|
if reedsolo and mode=="decrypt":
|
||||||
|
fin.read(1)
|
||||||
fout = open(outputFile,"wb+")
|
fout = open(outputFile,"wb+")
|
||||||
|
if reedsolo and mode=="encrypt":
|
||||||
|
print("Write +")
|
||||||
|
fout.write(b"+")
|
||||||
|
|
||||||
# Generate values for encryption if encrypting
|
# Generate values for encryption if encrypting
|
||||||
if mode=="encrypt":
|
if mode=="encrypt":
|
||||||
|
@ -254,17 +301,15 @@ def start():
|
||||||
salt = fin.read(16)
|
salt = fin.read(16)
|
||||||
nonce = fin.read(24)
|
nonce = fin.read(24)
|
||||||
|
|
||||||
# Show notice, set progress bar indeterminate
|
# Show notice about key derivation
|
||||||
statusString.set(derivingNotice)
|
statusString.set(derivingNotice)
|
||||||
progress.config(mode="indeterminate")
|
|
||||||
progress.start(15)
|
|
||||||
|
|
||||||
# Derive argon2id key
|
# Derive argon2id key
|
||||||
key = hash_secret_raw(
|
key = hash_secret_raw(
|
||||||
password,
|
password,
|
||||||
salt,
|
salt,
|
||||||
time_cost=8, # 8 iterations
|
time_cost=8, # 8 iterations
|
||||||
memory_cost=2**20, # 2^20 Kilobytes (1GB)
|
memory_cost=2**20, # 2^20 Kibibytes (1GiB)
|
||||||
parallelism=8, # 8 parallel threads
|
parallelism=8, # 8 parallel threads
|
||||||
hash_len=32,
|
hash_len=32,
|
||||||
type=Type.ID
|
type=Type.ID
|
||||||
|
@ -291,6 +336,7 @@ def start():
|
||||||
adArea["state"] = "normal"
|
adArea["state"] = "normal"
|
||||||
startBtn["state"] = "normal"
|
startBtn["state"] = "normal"
|
||||||
keepBtn["state"] = "normal"
|
keepBtn["state"] = "normal"
|
||||||
|
rsBtn["state"] = "normal"
|
||||||
working = False
|
working = False
|
||||||
del key
|
del key
|
||||||
return
|
return
|
||||||
|
@ -302,7 +348,6 @@ def start():
|
||||||
|
|
||||||
done = 0
|
done = 0
|
||||||
total = getsize(inputFile)
|
total = getsize(inputFile)
|
||||||
chunkSize = 2**20
|
|
||||||
startTime = datetime.now()
|
startTime = datetime.now()
|
||||||
|
|
||||||
# If secure wipe enabled, create a wiper object
|
# If secure wipe enabled, create a wiper object
|
||||||
|
@ -312,7 +357,11 @@ def start():
|
||||||
|
|
||||||
# Continously read file in chunks of 1MB
|
# Continously read file in chunks of 1MB
|
||||||
while True:
|
while True:
|
||||||
piece = fin.read(chunkSize)
|
if mode=="decrypt" and reedsolo:
|
||||||
|
# Read the piece and Reed-Solomon recovery bytes
|
||||||
|
piece = fin.read(1082544)
|
||||||
|
else:
|
||||||
|
piece = fin.read(chunkSize)
|
||||||
if wipe:
|
if wipe:
|
||||||
# If securely wipe, write random trash
|
# If securely wipe, write random trash
|
||||||
# to original file after reading it
|
# to original file after reading it
|
||||||
|
@ -326,7 +375,8 @@ def start():
|
||||||
fout.flush()
|
fout.flush()
|
||||||
fout.close()
|
fout.close()
|
||||||
fout = open(outputFile,"r+b")
|
fout = open(outputFile,"r+b")
|
||||||
fout.seek(len(str(len(ad)))+1+len(ad))
|
rsOffset = 1 if reedsolo else 0
|
||||||
|
fout.seek(len(str(len(ad)))+1+len(ad)+rsOffset)
|
||||||
fout.write(check)
|
fout.write(check)
|
||||||
fout.write(crc.digest())
|
fout.write(crc.digest())
|
||||||
fout.write(digest)
|
fout.write(digest)
|
||||||
|
@ -339,7 +389,7 @@ def start():
|
||||||
progress["value"] = 100
|
progress["value"] = 100
|
||||||
fin.close()
|
fin.close()
|
||||||
fout.close()
|
fout.close()
|
||||||
# If keep file checked...
|
# If keep file not checked...
|
||||||
if keep.get()!=1:
|
if keep.get()!=1:
|
||||||
remove(outputFile)
|
remove(outputFile)
|
||||||
selectFileInput["state"] = "normal"
|
selectFileInput["state"] = "normal"
|
||||||
|
@ -347,6 +397,7 @@ def start():
|
||||||
adArea["state"] = "normal"
|
adArea["state"] = "normal"
|
||||||
startBtn["state"] = "normal"
|
startBtn["state"] = "normal"
|
||||||
keepBtn["state"] = "normal"
|
keepBtn["state"] = "normal"
|
||||||
|
rsBtn["state"] = "normal"
|
||||||
working = False
|
working = False
|
||||||
del fin,fout,cipher,key
|
del fin,fout,cipher,key
|
||||||
return
|
return
|
||||||
|
@ -356,33 +407,69 @@ def start():
|
||||||
# Throws ValueError if incorrect
|
# Throws ValueError if incorrect
|
||||||
cipher.verify(digest)
|
cipher.verify(digest)
|
||||||
except:
|
except:
|
||||||
# File is modified
|
if not reedsoloErrorCount:
|
||||||
statusString.set(modifiedNotice)
|
# File is modified
|
||||||
progress["value"] = 100
|
statusString.set(modifiedNotice)
|
||||||
fin.close()
|
progress["value"] = 100
|
||||||
fout.close()
|
fin.close()
|
||||||
# If keep file checked...
|
fout.close()
|
||||||
if keep.get()!=1:
|
# If keep file not checked...
|
||||||
remove(outputFile)
|
if keep.get()!=1:
|
||||||
selectFileInput["state"] = "normal"
|
remove(outputFile)
|
||||||
passwordInput["state"] = "normal"
|
selectFileInput["state"] = "normal"
|
||||||
adArea["state"] = "normal"
|
passwordInput["state"] = "normal"
|
||||||
startBtn["state"] = "normal"
|
adArea["state"] = "normal"
|
||||||
keepBtn["state"] = "normal"
|
startBtn["state"] = "normal"
|
||||||
working = False
|
keepBtn["state"] = "normal"
|
||||||
del fin,fout,cipher,key
|
rsBtn["state"] = "normal"
|
||||||
return
|
working = False
|
||||||
else:
|
del fin,fout,cipher,key
|
||||||
kept = "modified"
|
return
|
||||||
|
else:
|
||||||
|
kept = "modified"
|
||||||
break
|
break
|
||||||
|
|
||||||
# Encrypt/decrypt chunk and update CRC
|
# Encrypt/decrypt chunk and update CRC
|
||||||
if mode=="encrypt":
|
if mode=="encrypt":
|
||||||
data = cipher.encrypt(piece)
|
data = cipher.encrypt(piece)
|
||||||
crc.update(data)
|
crc.update(data)
|
||||||
|
if reedsolo:
|
||||||
|
data = bytes(rsc.encode(data))
|
||||||
else:
|
else:
|
||||||
crc.update(piece)
|
if reedsolo:
|
||||||
data = cipher.decrypt(piece)
|
try:
|
||||||
|
data,_,fixed = rsc.decode(piece)
|
||||||
|
except ReedSolomonError:
|
||||||
|
# File is really corrupted
|
||||||
|
if not reedsoloErrorCount:
|
||||||
|
statusString.set(corruptedNotice)
|
||||||
|
progress["value"] = 100
|
||||||
|
# If keep file not checked...
|
||||||
|
if keep.get()!=1:
|
||||||
|
fin.close()
|
||||||
|
fout.close()
|
||||||
|
remove(outputFile)
|
||||||
|
selectFileInput["state"] = "normal"
|
||||||
|
passwordInput["state"] = "normal"
|
||||||
|
adArea["state"] = "normal"
|
||||||
|
startBtn["state"] = "normal"
|
||||||
|
keepBtn["state"] = "normal"
|
||||||
|
rsBtn["state"] = "normal"
|
||||||
|
working = False
|
||||||
|
del fin,fout,cipher,key
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
kept = "corrupted"
|
||||||
|
data = piece[:2**20]
|
||||||
|
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.
|
# Calculate speed, ETA, etc.
|
||||||
first = False
|
first = False
|
||||||
|
@ -396,12 +483,18 @@ def start():
|
||||||
if speed==0:
|
if speed==0:
|
||||||
first = True
|
first = True
|
||||||
speed = 0.1**6
|
speed = 0.1**6
|
||||||
rSpeed = round(speed)
|
rSpeed = round(speed,2)
|
||||||
eta = round((total-done)/(speed*10**6))
|
eta = round((total-done)/(speed*10**6))
|
||||||
|
if eta>=60:
|
||||||
|
eta = f"{eta//60}m {eta%60}"
|
||||||
if first:
|
if first:
|
||||||
statusString.set("...% at ... MB/s (ETA: ...s)")
|
statusString.set("...% at ... MB/s (ETA: ...s)")
|
||||||
else:
|
else:
|
||||||
info = f"{rPercent}% at {rSpeed} MB/s (ETA: {eta}s)"
|
info = f"{rPercent}% at {rSpeed} MB/s (ETA: {eta}s)"
|
||||||
|
if reedsolo and mode=="decrypt" and reedsoloFixedCount:
|
||||||
|
info += f", fixed {reedsoloFixedCount} corrupted bytes"
|
||||||
|
if reedsolo and mode=="decrypt" and reedsoloErrorCount:
|
||||||
|
info += f", {reedsoloErrorCount} MB unrecoverable"
|
||||||
statusString.set(info)
|
statusString.set(info)
|
||||||
|
|
||||||
done += chunkSize
|
done += chunkSize
|
||||||
|
@ -414,6 +507,9 @@ def start():
|
||||||
else:
|
else:
|
||||||
output = inputFile.split("/")[-1].replace(".pcf","")
|
output = inputFile.split("/")[-1].replace(".pcf","")
|
||||||
statusString.set(f"Completed. (Output: {output})")
|
statusString.set(f"Completed. (Output: {output})")
|
||||||
|
if mode=="decrypt" and reedsolo and reedsoloFixedCount:
|
||||||
|
statusString.set(f"Completed with {reedsoloFixedCount} bytes fixed."+
|
||||||
|
f" (Output: {output})")
|
||||||
else:
|
else:
|
||||||
if kept=="modified":
|
if kept=="modified":
|
||||||
statusString.set(kModifiedNotice)
|
statusString.set(kModifiedNotice)
|
||||||
|
@ -437,6 +533,8 @@ def start():
|
||||||
eraseBtn["state"] = "normal"
|
eraseBtn["state"] = "normal"
|
||||||
erase.set(0)
|
erase.set(0)
|
||||||
eraseBtn["state"] = "disabled"
|
eraseBtn["state"] = "disabled"
|
||||||
|
rs.set(0)
|
||||||
|
rsBtn["state"] = "disabled"
|
||||||
if not kept:
|
if not kept:
|
||||||
fout.flush()
|
fout.flush()
|
||||||
fsync(fout.fileno())
|
fsync(fout.fileno())
|
||||||
|
@ -463,12 +561,15 @@ def wrapper():
|
||||||
# Try start() and handle errors
|
# Try start() and handle errors
|
||||||
try:
|
try:
|
||||||
start()
|
start()
|
||||||
except:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
progress["value"] = 100
|
||||||
selectFileInput["state"] = "normal"
|
selectFileInput["state"] = "normal"
|
||||||
passwordInput["state"] = "normal"
|
passwordInput["state"] = "normal"
|
||||||
adArea["state"] = "normal"
|
adArea["state"] = "normal"
|
||||||
startBtn["state"] = "normal"
|
startBtn["state"] = "normal"
|
||||||
keepBtn["state"] = "normal"
|
keepBtn["state"] = "normal"
|
||||||
|
rsBtn["state"] = "normal"
|
||||||
statusString.set(unknownErrorNotice)
|
statusString.set(unknownErrorNotice)
|
||||||
dummy.focus()
|
dummy.focus()
|
||||||
working = False
|
working = False
|
||||||
|
@ -537,13 +638,26 @@ eraseBtn = tkinter.ttk.Checkbutton(
|
||||||
eraseBtn.place(x=18,y=260)
|
eraseBtn.place(x=18,y=260)
|
||||||
eraseBtn["state"] = "disabled"
|
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=280)
|
||||||
|
rsBtn["state"] = "disabled"
|
||||||
|
|
||||||
# Frame so start button can fill width
|
# Frame so start button can fill width
|
||||||
startFrame = tkinter.Frame(
|
startFrame = tkinter.Frame(
|
||||||
tk,
|
tk,
|
||||||
width=442,
|
width=442,
|
||||||
height=25
|
height=25
|
||||||
)
|
)
|
||||||
startFrame.place(x=19,y=290)
|
startFrame.place(x=19,y=310)
|
||||||
startFrame.columnconfigure(0,weight=10)
|
startFrame.columnconfigure(0,weight=10)
|
||||||
startFrame.grid_propagate(False)
|
startFrame.grid_propagate(False)
|
||||||
# Start button
|
# Start button
|
||||||
|
@ -562,7 +676,7 @@ progress = tkinter.ttk.Progressbar(
|
||||||
length=440,
|
length=440,
|
||||||
mode="determinate"
|
mode="determinate"
|
||||||
)
|
)
|
||||||
progress.place(x=20,y=328)
|
progress.place(x=20,y=348)
|
||||||
|
|
||||||
# Status label
|
# Status label
|
||||||
statusString = tkinter.StringVar(tk)
|
statusString = tkinter.StringVar(tk)
|
||||||
|
@ -571,7 +685,7 @@ status = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=statusString
|
textvariable=statusString
|
||||||
)
|
)
|
||||||
status.place(x=17,y=356)
|
status.place(x=17,y=376)
|
||||||
status.config(background="#f5f6f7")
|
status.config(background="#f5f6f7")
|
||||||
|
|
||||||
# Credits :)
|
# Credits :)
|
||||||
|
@ -585,20 +699,20 @@ credits = tkinter.ttk.Label(
|
||||||
)
|
)
|
||||||
credits["state"] = "disabled"
|
credits["state"] = "disabled"
|
||||||
credits.config(background="#f5f6f7")
|
credits.config(background="#f5f6f7")
|
||||||
credits.place(x=17,y=386)
|
credits.place(x=17,y=406)
|
||||||
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))
|
||||||
|
|
||||||
# Version
|
# Version
|
||||||
versionString = tkinter.StringVar(tk)
|
versionString = tkinter.StringVar(tk)
|
||||||
versionString.set("v1.7")
|
versionString.set("v1.8")
|
||||||
version = tkinter.ttk.Label(
|
version = tkinter.ttk.Label(
|
||||||
tk,
|
tk,
|
||||||
textvariable=versionString
|
textvariable=versionString
|
||||||
)
|
)
|
||||||
version["state"] = "disabled"
|
version["state"] = "disabled"
|
||||||
version.config(background="#f5f6f7")
|
version.config(background="#f5f6f7")
|
||||||
version.place(x=436,y=386)
|
version.place(x=436,y=406)
|
||||||
|
|
||||||
# Dummy button to remove focus from other buttons
|
# Dummy button to remove focus from other buttons
|
||||||
# and prevent ugly border highlighting
|
# and prevent ugly border highlighting
|
||||||
|
|
Loading…
Reference in New Issue