4
\$\begingroup\$

I am creating a simple program with a GUI to encrypt/decrypt text using different ciphers. I tried to make it so that it would be easy to add new ciphers.

This is my code:

import tkinter as tk
ALPHABET = "abcdefghijklmnopqrstuvwxyz"
class Cipher:
 # encode_fun and decode_fun should take in args (key, message).
 def __init__(self, encode_fun, decode_fun):
 self.encode_fun = encode_fun
 self.decode_fun = decode_fun
 self.output = None
 def encode(self, key, message):
 try:
 self.output.delete(0, "end")
 self.output.insert(0, self.encode_fun(key, message))
 except:
 return
 def decode(self, key, message):
 try:
 self.output.delete(0, "end")
 self.output.insert(0, self.decode_fun(key, message))
 except:
 return
 def display(self, master):
 frame = tk.Frame(master)
 frame.pack()
 encode_button = tk.Button(frame, text="Encode")
 encode_button.pack(side="left")
 decode_button = tk.Button(frame, text="Decode")
 decode_button.pack(side="right")
 tk.Label(frame, text="Key:").pack()
 key_text_box = tk.Entry(frame)
 key_text_box.pack()
 tk.Label(frame, text="Text:").pack()
 message_text_box = tk.Entry(frame)
 message_text_box.pack()
 self.output = tk.Entry(frame, background="gray", foreground="white")
 self.output.pack()
 encode_button.config(command=
 lambda: self.encode(key_text_box.get(), message_text_box.get()))
 decode_button.config(command=
 lambda: self.decode(key_text_box.get(), message_text_box.get()))
def caesar_shift_encode(key, message):
 encoded_message = ""
 for char in message.lower():
 try:
 encoded_message += ALPHABET[(ALPHABET.index(char) + int(key)) % len(ALPHABET)]
 except:
 encoded_message += char
 return encoded_message
def caesar_shift_decode(key, message):
 return caesar_shift_encode(str(-int(key)), message)
def xor_cipher_encode(key, message):
 return "".join(
 [ALPHABET[ALPHABET.index(key_char) ^ ALPHABET.index(message_char)]
 for key_char, message_char in zip(key, message)]
 )
root = tk.Tk()
caesar_shift = Cipher(caesar_shift_encode, caesar_shift_decode)
caesar_shift.display(root)
xor_cipher = Cipher(xor_cipher_encode, xor_cipher_encode)
xor_cipher.display(root)
tk.mainloop()

GUI:
what my GUI looks like

I would be grateful to hear about any improvements that could be made to this code.

asked Apr 10, 2023 at 16:49
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
ALPHABET = "abcdefghijklmnopqrstuvwxyz"

We don't like from string import ascii_lowercase ? Ok, whatever.


 self.output = None
 def encode(self, key, message):
 try:
 self.output.delete(0, "end")

I don't understand what's going on here.

Did we want to initialize output to the [] empty list, instead?

 self.output.insert(0, self.encode_fun(key, message))

Note that inserting at front is O(N), while appending at end is O(1). Consider using a deque.

[EDIT: Later we see self.output = tk.Entry(frame, .... Maybe the identifier output is on the vague side and could be renamed to suggest that it's a display window? Or maybe just a # commment here would help to better communicate the intent.]

 except:
 return

No.

This should apparently mention pass instead of return, though there's no difference in behavior ATM. A pass emphasizes to the Gentle Reader that we're evaluating this method for side effects. If we did want return for some reason, consider making it an explicit return None, since that is what happens. Prefer pass, consistent with the signature's (lack of) type hints.

Avoid "bare except". Prefer except Exception:, so as not to interfere with KeyboardException. Better yet, use except AttributeError: if that's what you're expecting, and fix the code defect which triggers "'NoneType' object has no attribute 'delete'" if that's what motivated the try.


Kudos on keeping the Tk graphic calls confined to .display(). For one thing, this makes it easy to add unit tests later on for encode / decode.

I also appreciate how everything is a local variable rather than defining self.this, self.that, and self.the_other. It reduces coupling, and means the Reader has fewer things to worry about when reading other bits of code.


Please add these type hints:

def caesar_shift_encode(key: str, message: str) -> str:

I initially expected key: int, based on the usual definition of a Caesar cipher.

 except:
 encoded_message += char

No.

Here, at least, it is apparent that Author's Intent was to cope with "ValueError: substring not found". So say that explicitly. In any event, don't use a bare except.


def xor_cipher_encode(key, message):

This is an odd signature. It suggests parallel structure with the Caesar signature that accepted a single integer f"{key}". But it's not that.

In a crypto context we might expect 128 bits or 512 bits of high-entropy keying material. But it's not that.

It turns out the "key" is a "one-time pad". Ok. I'm just surprised is all.

Validate that len(otp) >= len(message), so caller won't be surprised by silent truncation.


root = tk.Tk() ...

Please protect this with a guard:

if __name__ == "__main__":
 root = tk.Tk() ...

Why?

At some point you or a collaborator will want to write a unit test, which should be able to import this without side effects.


This program accomplishes its design goals, with fairly clear and mostly bug free code.

I would be happy to delegate or accept maintenance tasks on this codebase.

answered Apr 11, 2023 at 1:04
\$\endgroup\$
1
  • 1
    \$\begingroup\$ self.output is an instance of tk.Entry. (I need to access it to display the encrypted/decrypted message. And self.output.delete(0, "end") clears the entry so that self.output.insert(0, ...) can insert the encrypted/decrypted text in (to replace the previous text). \$\endgroup\$ Commented Apr 11, 2023 at 5:34

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.