Update Picocrypt.py

This commit is contained in:
Evan Su 2021-03-16 12:10:54 -04:00 committed by GitHub
parent 64401e8a56
commit c0fceed342
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 46 additions and 19 deletions

View File

@ -3,7 +3,7 @@
""" """
Dependencies: argon2-cffi, pycryptodome, reedsolo 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
""" """
@ -24,6 +24,7 @@ except:
except: except:
# Fedora # Fedora
system("sudo dnf install python3-tkinter") system("sudo dnf install python3-tkinter")
system("python3 -m pip install argon2-cffi --no-cache-dir") system("python3 -m pip install argon2-cffi --no-cache-dir")
system("python3 -m pip install pycryptodome --no-cache-dir") system("python3 -m pip install pycryptodome --no-cache-dir")
system("python3 -m pip install reedsolo --no-cache-dir") system("python3 -m pip install reedsolo --no-cache-dir")
@ -48,8 +49,8 @@ try:
except: except:
from reedsolo import RSCodec,ReedSolomonError from reedsolo import RSCodec,ReedSolomonError
# Tk/Tcl is a little barbaric, disable # Tk/Tcl is a little barbaric, so I'm disabling
# high DPI so it doesn't look really ugly # high DPI so it doesn't scale bad and look horrible
try: try:
from ctypes import windll from ctypes import windll
windll.shcore.SetProcessDpiAwareness(0) windll.shcore.SetProcessDpiAwareness(0)
@ -84,7 +85,7 @@ tk.title("Picocrypt")
tk.configure(background="#f5f6f7") tk.configure(background="#f5f6f7")
tk.resizable(0,0) tk.resizable(0,0)
# Try setting image if included with Picocrypt # Try setting window icon if included with Picocrypt
try: try:
favicon = tkinter.PhotoImage(file="./key.png") favicon = tkinter.PhotoImage(file="./key.png")
tk.iconphoto(False,favicon) tk.iconphoto(False,favicon)
@ -222,15 +223,17 @@ def start():
reedsolo = rs.get()==1 reedsolo = rs.get()==1
else: else:
mode = "decrypt" mode = "decrypt"
# Check if Reed-Solomon was enabled by checking for "+"
test = open(inputFile,"rb+") test = open(inputFile,"rb+")
decider = test.read(1).decode("utf-8") decider = test.read(1).decode("utf-8")
test.close() test.close()
if decider=="+": if decider=="+":
reedsolo = True reedsolo = True
print("reed solo") print("reed solo")
# Decrypted output is just input file without the extension
outputFile = inputFile[:-4] outputFile = inputFile[:-4]
# Check if file already exists # Check if file already exists (getsize() throws error if file not found)
try: try:
getsize(outputFile) getsize(outputFile)
force = messagebox.askyesno("Warning",overwriteNotice) force = messagebox.askyesno("Warning",overwriteNotice)
@ -247,6 +250,7 @@ def start():
# Create Reed-Solomon object # Create Reed-Solomon object
if reedsolo: if reedsolo:
statusString.set(rscNotice) statusString.set(rscNotice)
# 8 bytes per 128 bytes, 6.25% larger output file
rsc = RSCodec(8) rsc = RSCodec(8)
reedsoloFixedCount = 0 reedsoloFixedCount = 0
reedsoloErrorCount = 0 reedsoloErrorCount = 0
@ -258,6 +262,7 @@ def start():
ad = adArea.get("1.0",tkinter.END).encode("utf-8") ad = adArea.get("1.0",tkinter.END).encode("utf-8")
wipe = erase.get()==1 wipe = erase.get()==1
# Disable inputs and buttons while encrypting/decrypting
selectFileInput["state"] = "disabled" selectFileInput["state"] = "disabled"
passwordInput["state"] = "disabled" passwordInput["state"] = "disabled"
adArea["state"] = "disabled" adArea["state"] = "disabled"
@ -266,26 +271,29 @@ def start():
keepBtn["state"] = "disabled" keepBtn["state"] = "disabled"
rsBtn["state"] = "disabled" rsBtn["state"] = "disabled"
# Open files
fin = open(inputFile,"rb+") fin = open(inputFile,"rb+")
if reedsolo and mode=="decrypt": if reedsolo and mode=="decrypt":
# Move pointer one forward
fin.read(1) fin.read(1)
fout = open(outputFile,"wb+") fout = open(outputFile,"wb+")
if reedsolo and mode=="encrypt": if reedsolo and mode=="encrypt":
print("Write +") # Signal that Reed-Solomon was enabled with "+"
fout.write(b"+") fout.write(b"+")
# Generate values for encryption if encrypting # Generate values for encryption if encrypting
if mode=="encrypt": if mode=="encrypt":
salt = urandom(16) salt = urandom(16)
nonce = urandom(24) nonce = urandom(24)
fout.write(str(len(ad)).encode("utf-8")) fout.write(str(len(ad)).encode("utf-8")) # Length of metadata
fout.write(b"|") fout.write(b"|") # Separator
fout.write(ad) fout.write(ad) # Metadata
fout.write(b"0"*64) # Write zeros as placeholder, come back to write over it later
fout.write(b"0"*64) fout.write(b"0"*64) # SHA3-512 of encryption key
fout.write(b"0"*16) fout.write(b"0"*64) # CRC of file
fout.write(salt) fout.write(b"0"*16) # Poly1305 tag
fout.write(nonce) fout.write(salt) # Argon2 salt
fout.write(nonce) # ChaCha20 nonce
# If decrypting, read values from file # If decrypting, read values from file
else: else:
# Read past metadata into actual data # Read past metadata into actual data
@ -297,6 +305,7 @@ def start():
adlen = adlen[:-1] adlen = adlen[:-1]
break break
fin.read(int(adlen.decode("utf-8"))) fin.read(int(adlen.decode("utf-8")))
# Read the salt, nonce, etc.
cs = fin.read(64) cs = fin.read(64)
crccs = fin.read(64) crccs = fin.read(64)
digest = fin.read(16) digest = fin.read(16)
@ -333,6 +342,7 @@ def start():
fin.close() fin.close()
fout.close() fout.close()
remove(outputFile) remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal" selectFileInput["state"] = "normal"
passwordInput["state"] = "normal" passwordInput["state"] = "normal"
adArea["state"] = "normal" adArea["state"] = "normal"
@ -348,6 +358,7 @@ def start():
# Cyclic redundancy check for file corruption # Cyclic redundancy check for file corruption
crc = sha3_512() crc = sha3_512()
# Amount of data encrypted/decrypted, total file size, starting time
done = 0 done = 0
total = getsize(inputFile) total = getsize(inputFile)
startTime = datetime.now() startTime = datetime.now()
@ -360,7 +371,7 @@ def start():
# Continously read file in chunks of 1MB # Continously read file in chunks of 1MB
while True: while True:
if mode=="decrypt" and reedsolo: if mode=="decrypt" and reedsolo:
# Read the piece and Reed-Solomon recovery bytes # Read a chunk plus Reed-Solomon recovery bytes
piece = fin.read(1082544) piece = fin.read(1082544)
else: else:
piece = fin.read(chunkSize) piece = fin.read(chunkSize)
@ -372,13 +383,15 @@ def start():
# If EOF # If EOF
if not piece: if not piece:
if mode=="encrypt": if mode=="encrypt":
# Get the cipher MAC tag, write to file # Get the cipher MAC tag (Poly1305)
digest = cipher.digest() digest = cipher.digest()
fout.flush() fout.flush()
fout.close() fout.close()
fout = open(outputFile,"r+b") fout = open(outputFile,"r+b")
# Compute the offset and seek to it
rsOffset = 1 if reedsolo else 0 rsOffset = 1 if reedsolo else 0
fout.seek(len(str(len(ad)))+1+len(ad)+rsOffset) fout.seek(len(str(len(ad)))+1+len(ad)+rsOffset)
# Write hash of key, CRC, and Poly1305 MAC tag
fout.write(check) fout.write(check)
fout.write(crc.digest()) fout.write(crc.digest())
fout.write(digest) fout.write(digest)
@ -394,6 +407,7 @@ def start():
# If keep file not checked... # If keep file not checked...
if keep.get()!=1: if keep.get()!=1:
remove(outputFile) remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal" selectFileInput["state"] = "normal"
passwordInput["state"] = "normal" passwordInput["state"] = "normal"
adArea["state"] = "normal" adArea["state"] = "normal"
@ -406,7 +420,7 @@ def start():
else: else:
kept = "corrupted" kept = "corrupted"
try: try:
# Throws ValueError if incorrect # Throws ValueError if incorrect Poly1305
cipher.verify(digest) cipher.verify(digest)
except: except:
if not reedsoloErrorCount: if not reedsoloErrorCount:
@ -418,6 +432,7 @@ def start():
# If keep file not checked... # If keep file not checked...
if keep.get()!=1: if keep.get()!=1:
remove(outputFile) remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal" selectFileInput["state"] = "normal"
passwordInput["state"] = "normal" passwordInput["state"] = "normal"
adArea["state"] = "normal" adArea["state"] = "normal"
@ -433,11 +448,15 @@ def start():
# Encrypt/decrypt chunk and update CRC # Encrypt/decrypt chunk and update CRC
if mode=="encrypt": if mode=="encrypt":
# Encrypt piece
data = cipher.encrypt(piece) data = cipher.encrypt(piece)
# Update checksum
crc.update(data) crc.update(data)
if reedsolo: if reedsolo:
# Encode using Reed-Solomon if user chooses
data = bytes(rsc.encode(data)) data = bytes(rsc.encode(data))
else: else:
# Basically encrypting but in reverse
if reedsolo: if reedsolo:
try: try:
data,_,fixed = rsc.decode(piece) data,_,fixed = rsc.decode(piece)
@ -451,6 +470,7 @@ def start():
fin.close() fin.close()
fout.close() fout.close()
remove(outputFile) remove(outputFile)
# Reset UI
selectFileInput["state"] = "normal" selectFileInput["state"] = "normal"
passwordInput["state"] = "normal" passwordInput["state"] = "normal"
adArea["state"] = "normal" adArea["state"] = "normal"
@ -476,22 +496,27 @@ def start():
# Calculate speed, ETA, etc. # Calculate speed, ETA, etc.
first = False first = False
elapsed = (datetime.now()-startTime).total_seconds() elapsed = (datetime.now()-startTime).total_seconds()
if elapsed==0: # Prevent divison by zero
if not elapsed:
elapsed = 0.1**6 elapsed = 0.1**6
percent = done*100/total percent = done*100/total
progress["value"] = percent progress["value"] = percent
rPercent = round(percent) rPercent = round(percent)
speed = (done/elapsed)/10**6 speed = (done/elapsed)/10**6
if speed==0: # Prevent divison by zero
if not speed:
first = True first = True
speed = 0.1**6 speed = 0.1**6
rSpeed = round(speed,2) rSpeed = round(speed,2)
eta = round((total-done)/(speed*10**6)) eta = round((total-done)/(speed*10**6))
# Seconds to minutes if seconds more than 59
if eta>=60: if eta>=60:
eta = f"{eta//60}m {eta%60}" eta = f"{eta//60}m {eta%60}"
# If it's the first round and no data/predictions yet...
if first: if first:
statusString.set("...% at ... MB/s (ETA: ...s)") statusString.set("...% at ... MB/s (ETA: ...s)")
else: else:
# Update status
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: if reedsolo and mode=="decrypt" and reedsoloFixedCount:
info += f", fixed {reedsoloFixedCount} corrupted bytes" info += f", fixed {reedsoloFixedCount} corrupted bytes"
@ -499,6 +524,7 @@ def start():
info += f", {reedsoloErrorCount} MB unrecoverable" info += f", {reedsoloErrorCount} MB unrecoverable"
statusString.set(info) statusString.set(info)
# Increase done and write to output
done += chunkSize done += chunkSize
fout.write(data) fout.write(data)
@ -509,6 +535,7 @@ 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})")
# Show Reed-Solomon stats if it fixed corrupted bytes
if mode=="decrypt" and reedsolo and reedsoloFixedCount: if mode=="decrypt" and reedsolo and reedsoloFixedCount:
statusString.set(f"Completed with {reedsoloFixedCount} bytes fixed."+ statusString.set(f"Completed with {reedsoloFixedCount} bytes fixed."+
f" (Output: {output})") f" (Output: {output})")