diff --git a/app_local/McEliece_GUI.py b/app_local/McEliece_GUI.py new file mode 100644 index 0000000..01b8d79 --- /dev/null +++ b/app_local/McEliece_GUI.py @@ -0,0 +1,130 @@ +#pyinstaller -F -w -i "icon.ico" McEliece_GUI.py + +import cryptosystem_core as core +import tkinter as tk + +def mymessagebox(fontsize, butsize, mytitle, mytext): + toplevel = tk.Toplevel(window) + toplevel.title(mytitle) + toplevel.geometry(f"600x400+{window.winfo_x()+40}+{window.winfo_y()+15}") + toplevel.resizable(False, False) + l = tk.Label(toplevel, text = mytext, font = ("TkDefaultFont", fontsize)) + l.pack() + b = tk.Button(toplevel, text = "Close", command = toplevel.destroy, width = 10, font = ("TkDefaultFont", butsize)) + b.pack() + +def show_error(): + mymessagebox(30, 30, "Error!", "ERROR!\n\nPress help button\nto show common\nmistakes in usage.\n") + +def button0_click(): + mymessagebox(15, 15, "Source code: github.com/vovuas2003/McEliece", "Program can work slow and doesn't response, it is normal.\n1st line = current configuration of cryptosystem.\n2nd line = public key (send to anybody).\n3rd line = first half (s) of private key (keep in secret!).\n4th line = second half (p) of private key (keep in secret!).\nBig field = place for writing text or pasting message.\nGenerate keys = rewrite all 3 keys.\nEncrypt = pubkey is required.\nDecrypt = both privkeys are required.\nYou can restore any key from the other two.\nConfig = change cryptosystem parameters n and k.\n(two numbers separated by a space, default: 255 210)\n(255 mod n = 0; 255 = 3 * 5 * 17)\n(2 <= k <= n; (n - k) div 2 bytes are randomly changed)\nPT! = just easter egg :)") + +def button1_click(): + try: + new_text1, new_text2, new_text3 = core.generate() + entry1.delete(0, tk.END) + entry1.insert(0, new_text1) + entry2.delete(0, tk.END) + entry2.insert(0, new_text2) + entry3.delete(0, tk.END) + entry3.insert(0, new_text3) + except: + show_error() + +def button2_click(): + try: + G = entry1.get() + text = entry4.get("1.0", "end-1c") + new_text4 = core.encrypt(G, text) + entry4.delete("1.0", tk.END) + entry4.insert("1.0", new_text4) + except: + show_error() + +def button3_click(): + try: + S = entry2.get() + P = entry3.get() + msg = entry4.get("1.0", "end-1c") + new_text4 = core.decrypt(S, P, msg) + entry4.delete("1.0", tk.END) + entry4.insert("1.0", new_text4) + except: + show_error() + +def button4_click(): + try: + S = entry2.get() + P = entry3.get() + new_text1 = core.restore_G(S, P) + entry1.delete(0, tk.END) + entry1.insert(0, new_text1) + except: + show_error() + +def button5_click(): + try: + G = entry1.get() + P = entry3.get() + new_text2 = core.break_S(G, P) + entry2.delete(0, tk.END) + entry2.insert(0, new_text2) + except: + show_error() + +def button6_click(): + try: + G = entry1.get() + S = entry2.get() + new_text3 = core.break_P(G, S) + entry3.delete(0, tk.END) + entry3.insert(0, new_text3) + except: + show_error() + +def button7_click(): + try: + core.config(entry0.get()) + except: + entry0.delete(0, tk.END) + entry0.insert(0, str(core.n) + " " + str(core.k)) + show_error() + +def button8_click(): + mymessagebox(200, 15, "PT!" * 25, "PT!") + +window = tk.Tk() +window.title("McEliece by vovuas2003") + +frame_buttons = tk.Frame(window) +frame_buttons.pack(side = tk.TOP, fill = tk.X) + +buttons = [] +but_names = ["help", "generate keys", "encrypt text", "decrypt message", "pubkey = s + p", "s = pubkey + p", "p = pubkey + s", "change config", "PT!"] +but_com = [button0_click, button1_click, button2_click, button3_click, button4_click, button5_click, button6_click, button7_click, button8_click] +for i in range(9): + buttons.append(tk.Button(frame_buttons, text = but_names[i], command = but_com[i])) + +for button in buttons: + button.pack(side = tk.LEFT) + +entry0 = tk.Entry(window, width = 50) +entry1 = tk.Entry(window, width = 50) +entry2 = tk.Entry(window, width = 50) +entry3 = tk.Entry(window, width = 50) +entry4 = tk.Text(window, height = 20, width = 50) + +entry0.insert(0, "255 210 (default config)") +entry1.insert(0, "pubkey") +entry2.insert(0, "privkey s") +entry3.insert(0, "privkey p") +entry4.insert("1.0", "Write a text or paste a message here!\nAll utf-8 symbols are supported, e.g. alt codes and Russian text.\n\n(use ctrl+a before crtl+v or ctrl+c)\n(switch keyboard layout to english to use these shortcuts)\n\nYou can't resize window, but can scroll down.\nProgram can work slow, don't kill it please :)") + +entry0.pack() +entry1.pack() +entry2.pack() +entry3.pack() +entry4.pack(fill = tk.BOTH, expand = True) + +window.resizable(False, False) +window.mainloop() diff --git a/app_local/McEliece_console.py b/app_local/McEliece_console.py new file mode 100644 index 0000000..db73e5e --- /dev/null +++ b/app_local/McEliece_console.py @@ -0,0 +1,262 @@ +#pyinstaller -F -i "icon.ico" McEliece_console.py + +import getpass +import random + +def main(): + safe_start() + +def safe_start(): + try: + start_menu() + except: + print("\nUnknown error (maybe ctrl+c), emergency exit!") + +def start_menu(): + f = True + print("\nA soldering iron is into a black hole.") + #thermorectal cryptanalysis + if myhash(getpass.getpass("Login: ")) != 1314399736851798576: + f = False + if myhash(getpass.getpass("Password: ")) != 192441972608755898: + f = False + if f: + print("Authorization successful, wait a bit.") + menu() + else: + print("Permission denied.") + print("\nPress ENTER to exit.", end = '') + input() + +def menu(): + import cryptosystem_core as core + print("\nMcEliece cryptosystem implementation by vovuas2003.\n") + print("All necessary txt files must be in utf-8 and located in the directory with this exe program.\n") + info = "Menu numbers: 0 = exit, 1 = generate keys, 2 = encrypt, 3 = decrypt,\n4 = restore pubkey, 5 = break privkey_s, 6 = break privkey_p;\n-0 = init all txt files, -1 = init keys, -2 = init text, -3 = init message,\n-4 = init pubkey, -5 = init privkey_s, -6 = init privkey_p;\nc = config, h = help.\n" + err = "Error! Check command info and try again!\n" + ok = "Operation successful.\n" + inp = [str(i) for i in range(7)] + ['-' + str(i) for i in range(7)] + ['c', 'h'] + ['1337'] + print(info) + while True: + s = input("Menu number: ") + while s not in inp: + s = input("Wrong menu number, h = help: ") + if s == 'h': + print(info) + elif s == 'c': + print("Default config is 255 210, current is " + str(core.n) + " " + str(core.k) + ". Change config?") + if(not get_yes_no()): + continue + try: + print("Config is two numbers n >= k >= 2; (3 * 5 * 17) mod n = 0.") + core.config(input("Write n and k separated by a space: ")) + print(ok) + except: + print(err) + elif s == '0': + print("\nGood luck!") + break + elif s == '1': + print("This operation will rewrite pubkey.txt, privkey_s.txt and privkey_p.txt; are you sure?") + if(not get_yes_no()): + continue + try: + G, S, P = core.generate() + write_txt("pubkey", G) + write_txt("privkey_s", S) + write_txt("privkey_p", P) + print(ok) + except: + print(err) + elif s == '2': + print("Write your text into text.txt; pubkey.txt is required, message.txt will be rewritten.") + if(not get_yes_no()): + continue + try: + G = read_txt("pubkey") + text = read_txt("text") + msg = core.encrypt(G, text) + write_txt("message", msg) + print(ok) + except: + print(err) + elif s == '3': + print("You need message.txt, privkey_s.txt and privkey_p.txt; text.txt will be rewritten.") + if(not get_yes_no()): + continue + try: + S = read_txt("privkey_s") + P = read_txt("privkey_p") + msg = read_txt("message") + text = core.decrypt(S, P, msg) + write_txt("text", text) + print(ok) + except: + print(err) + elif s == '4': + print("You need privkey_s.txt and privkey_p.txt; pubkey.txt will be rewritten.") + if(not get_yes_no()): + continue + try: + S = read_txt("privkey_s") + P = read_txt("privkey_p") + G = core.restore_G(S, P) + write_txt("pubkey", G) + print(ok) + except: + print(err) + elif s == '5': + print("You need pubkey.txt and privkey_p.txt; privkey_s.txt will be rewritten.") + if(not get_yes_no()): + continue + try: + G = read_txt("pubkey") + P = read_txt("privkey_p") + S = core.break_S(G, P) + write_txt("privkey_s", S) + print(ok) + except: + print(err) + elif s == '6': + print("You need pubkey.txt and privkey_s.txt; privkey_p.txt will be rewritten.") + if(not get_yes_no()): + continue + try: + G = read_txt("pubkey") + S = read_txt("privkey_s") + P = core.break_P(G, S) + write_txt("privkey_p", P) + print(ok) + except: + print(err) + elif s == '-0': + print("Create (or make empty) all 5 necessary txt files in right utf-8 encoding.") + if(not get_yes_no()): + continue + try: + write_txt("pubkey", "") + write_txt("privkey_s", "") + write_txt("privkey_p", "") + write_txt("text", "") + write_txt("message", "") + print(ok) + except: + print(err) + elif s == '-1': + print("Create (or make empty) all 3 keys txt files in right utf-8 encoding.") + if(not get_yes_no()): + continue + try: + write_txt("pubkey", "") + write_txt("privkey_s", "") + write_txt("privkey_p", "") + print(ok) + except: + print(err) + elif s == '-2': + print("Create (or make empty) text.txt in right utf-8 encoding.") + if(not get_yes_no()): + continue + try: + write_txt("text", "") + print(ok) + except: + print(err) + elif s == '-3': + print("Create (or make empty) message.txt in right utf-8 encoding.") + if(not get_yes_no()): + continue + try: + write_txt("message", "") + print(ok) + except: + print(err) + elif s == '-4': + print("Create (or make empty) pubkey.txt in right utf-8 encoding.") + if(not get_yes_no()): + continue + try: + write_txt("pubkey", "") + print(ok) + except: + print(err) + elif s == '-5': + print("Create (or make empty) privkey_s.txt in right utf-8 encoding.") + if(not get_yes_no()): + continue + try: + write_txt("privkey_s", "") + print(ok) + except: + print(err) + elif s == '-6': + print("Create (or make empty) privkey_p.txt in right utf-8 encoding.") + if(not get_yes_no()): + continue + try: + write_txt("privkey_p", "") + print(ok) + except: + print(err) + elif s == '1337': + c = input("Move the soldering iron into the black hole number: ") + try: + PT(int(c)) + except: + print("Iron: 'I don't know this hole.'") + continue + else: + print("Impossible behaviour, mistake in source code!\nThe string allowed in the inp array is not bound to the call of any function!") + break + +def get_yes_no(): + s = input("Confirm (0 = go back, 1 = continue): ") + while s not in ['0', '1']: + s = input("Try again, 0 or 1: ") + return int(s) + +def myhash(s, m = 2**61 - 1, p = 257): + a = 0 + for i in range(len(s)): + a = ((a * p) % m + ord(s[i])) % m + return a + +def PT(m, M = 3): + if m == 0: + print("Iron: 'OK, I will choose the number by myself.'") + while m == 0: + m = random.randint(-M, M) + s = "PT!" + p = " " + f = False + if m < 0: + s, p = p, s + m *= -1 + f = True + if m > M: + print("Iron: 'Are you sure to move me so far?'") + if(not get_yes_no()): + return + print() + if f: + print(p * (10 * m + 1)) + print(p + (s * 3 + p + s * 3 + p + s + p) * m) + print(p + (s + p + s + p * 2 + s + p * 2 + s + p) * m) + print(p + (s * 3 + p * 2 + s + p * 2 + s + p) * m) + print(p + (s + p * 4 + s + p * 4) * m) + print(p + (s + p * 4 + s + p * 2 + s + p) * m) + if f: + print(p * (10 * m + 1)) + print() + +def write_txt(name, string): + with open(name + ".txt", "w", encoding = "utf-8") as f: + f.write(string) + +def read_txt(name): + with open(name + ".txt", "r", encoding = "utf-8") as f: + out = f.read() + return out + +if __name__ == "__main__": + main() diff --git a/app_local/break.py b/app_local/break.py deleted file mode 100644 index 091f92f..0000000 --- a/app_local/break.py +++ /dev/null @@ -1,61 +0,0 @@ -#break cryptosystem if know a half of private key - -import numpy as np -import galois - -import pubkey -import privkey -import message - -n = 127 -k = 32 -order = 2 ** 7 -GF = galois.GF(order) - -def main(): - G_ = GF(pubkey.get()) #we need to know a public matrix - P = GF(privkey.get_P()) #and P - only one of two private matrices - S = break_S(P, G_) #to calculate S - the second part of private key - c = GF(message.get()) - print(decode(S, P, c)) #and decode the message - -def unpad_message(msg): - padding_byte = msg[-1] - for i in range(1, padding_byte + 1): - if msg[-i] != padding_byte: - raise ValueError("Incorrect padding!") - return msg[:-padding_byte] - -def my_fix(A): - #make square matrix by deleting right columns - l = len(A) - r = GF.Zeros((l, l)) - for i in range(l): - for j in range(l): - r[i][j] = A[i][j] - return r - -def decode(S, P, c): - rs = galois.ReedSolomon(n, k, field=GF) - c = c @ np.linalg.inv(P) - c = rs.decode(c) - c = c @ np.linalg.inv(S) - c = [int(i) for i in c] - c = unpad_message(c) - c = bytes(c) - c = c.decode() - return c - -def break_S(P, G_): - return my_fix(G_ @ np.linalg.inv(P)) #works for Reed-Solomon - #G_ = S @ G @ P - rs = galois.ReedSolomon(n, k, field=GF) - G = rs.G - G_ = G_ @ np.linalg.inv(P) - G_ = my_fix(G_) - G = my_fix(G) #returns E because we use Reed-Solomon algo - S = G_ @ np.linalg.inv(G) - return S - -if __name__ == "__main__": - main() diff --git a/app_local/portable_v3_core.py b/app_local/cryptosystem_core.py similarity index 62% rename from app_local/portable_v3_core.py rename to app_local/cryptosystem_core.py index 6404af2..6936317 100644 --- a/app_local/portable_v3_core.py +++ b/app_local/cryptosystem_core.py @@ -1,24 +1,58 @@ -#G = pubkey; S and P = privkeys; text = plaintext; msg = encrypted text -#all these variables are strings +#McEliece cryptosystem implementation by vovuas2003 + #Usage: -#G, S, P = generate() -#msg = encrypt(G, text) -#text = decrypt(S, P, msg) -#G = restore_G(S, P) -#S = break_S(G, P) -#P = break_P(G, S) +#G = pubkey; S and P = privkeys; text = plaintext; msg = encrypted text +#all these variables are strings, so it's easy to integrate this code into any project (check GUI and console examples) +#text must be in utf-8 encoding (e.g. force encoding when open txt files, check console example) +#keys and messages are saved as base64 strings + +''' +import cryptosystem_core as core +G, S, P = core.generate() +msg = core.encrypt(G, text) +text = core.decrypt(S, P, msg) +G = core.restore_G(S, P) +S = core.break_S(G, P) +P = core.break_P(G, S) +core.config("255 210") #this is the default configuration, NO NEED TO WRITE THIS LINE FOR INITIALIZATION, it is just an example of using the function +#these parameters n and k affect the length of the keys and the number of erroneous bytes in the message +#see the comments below to understand the requirements for n and k +''' + +#if you want to figure out how the code below works, keep in mind that +#G_ is a pubkey, G is a Reed Solomon code matrix and P is saved as permutation array +#to use the core of the cryptosystem, you don't need to think about it, just write the code as in the example above import numpy as np import galois import random import base64 -order = 256 -n = 255 -k = 210 -GF = galois.GF(2, 8, irreducible_poly = "x^8 + x^4 + x^3 + x^2 + 1", primitive_element = "x", verify = False) +order = 256 #p^m = 2**8; encrypt each byte and save in base64 format +n = 255 #(order - 1) mod n = 0 for Reed Solomon code; 255 = 3 * 5 * 17 = (order - 1) +k = 210 #2 <= k <= n; randomly change (n - k) div 2 bytes during encryption +GF = galois.GF(2, 8, irreducible_poly = "x^8 + x^4 + x^3 + x^2 + 1", primitive_element = "x", verify = False) #hardcoded galois.GF(2**8).properties for pyinstaller rs = galois.ReedSolomon(n, k, field = GF) +def main(): #for testing + pass + +def config(string): + global n + global k + global rs + try: + nn, kk = map(int, string.split()[:2]) + if kk < 2: + raise Exception() + rrss = galois.ReedSolomon(nn, kk, field = GF) + except: + raise Exception() + else: + n = nn + k = kk + rs = rrss + def generate(): S = generate_S() G = rs.G @@ -44,17 +78,16 @@ def generate_P(): def write_pubkey(G_): rows = [bytes(row) for row in G_] - output = "".join([base64.b64encode(row).decode() for row in rows]) - return output + row = bytes([i for j in rows for i in j]) + return base64.b64encode(row).decode() def write_privkey_s(S): rows = [bytes(row) for row in S] - output = "".join([base64.b64encode(row).decode() for row in rows]) - return output + row = bytes([i for j in rows for i in j]) + return base64.b64encode(row).decode() def write_privkey_p(p): - output = base64.b64encode(bytes(p)).decode() - return output + return base64.b64encode(bytes(p)).decode() def read_pubkey(out): out = [int(i) for i in base64.b64decode(out)] @@ -95,14 +128,14 @@ def unpad_message(msg): def encrypt(key, text): G_ = GF(read_pubkey(key)) - text = text.encode() - out = "" + text = text.encode("utf-8") + out = bytes() while len(text) > k - 1: tmp = text[: k - 1] text = text[k - 1 :] out += encrypt_one(G_, tmp) out += encrypt_one(G_, text) - return out + return base64.b64encode(out).decode() def encrypt_one(G_, text): msg = pad_message(text, k) @@ -116,7 +149,7 @@ def encrypt_one(G_, text): z[ind] += random.randint(1, order - 1) z[ind] %= order c = c + GF(z) - return base64.b64encode(bytes(c)).decode() + return bytes(c) def decrypt(s, p, msg): S_inv = np.linalg.inv(GF(read_privkey_s(s))) @@ -125,7 +158,7 @@ def decrypt(s, p, msg): msg = [msg[i - n : i] for i in range(n, len(msg) + n, n)] msg = [decrypt_one(S_inv, P_inv, GF(i)) for i in msg] msg = [i for j in msg for i in j] - msg = bytes(msg).decode() + msg = bytes(msg).decode("utf-8") return msg def decrypt_one(S_inv, P_inv, msg): @@ -176,3 +209,6 @@ def break_P(key, s): #print("Wrong pubkey and privkey_s combination!") raise Exception() return write_privkey_p(p) + +if __name__ == "__main__": + main() diff --git a/app_local/decode.py b/app_local/decode.py deleted file mode 100644 index 814b3ec..0000000 --- a/app_local/decode.py +++ /dev/null @@ -1,37 +0,0 @@ -import numpy as np -import galois - -import privkey -import message - -n = 127 -k = 32 -order = 2 ** 7 -GF = galois.GF(order) - -def main(): - S = GF(privkey.get_S()) - P = GF(privkey.get_P()) - c = GF(message.get()) - print(decode(S, P, c)) - -def unpad_message(msg): - padding_byte = msg[-1] - for i in range(1, padding_byte + 1): - if msg[-i] != padding_byte: - raise ValueError("Incorrect padding!") - return msg[:-padding_byte] - -def decode(S, P, c): - rs = galois.ReedSolomon(n, k, field=GF) - c = c @ np.linalg.inv(P) - c = rs.decode(c) - c = c @ np.linalg.inv(S) - c = [int(i) for i in c] - c = unpad_message(c) - c = bytes(c) - c = c.decode() - return c - -if __name__ == "__main__": - main() diff --git a/app_local/encode.py b/app_local/encode.py deleted file mode 100644 index 29cc698..0000000 --- a/app_local/encode.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np -import galois -import random - -import pubkey - -n = 127 -k = 32 -order = 2 ** 7 -GF = galois.GF(order) - -def main(): - G_ = GF(pubkey.get()) - print("Message to encode (max len = k-1 = 31):") - message = input() - if len(message) > k-1: - print("Message is too long!") - return - ct = encrypt(G_, message) - ct = list(map(int, ct)) - export(ct) - print("Done!") - -def pad_message(msg: bytes, pad_size: int) -> list[int]: - padding = pad_size - (len(msg) % pad_size) - return list(msg + padding.to_bytes() * padding) - -def encrypt(G_, text): - msg = pad_message(text.encode(), k) - m = GF(msg) - c = m.T @ G_ - t = (n - k) // 2 - z = np.zeros(n, dtype = int) - p = [i for i in range(n)] - for i in range(t): - z[p.pop(random.randint(0, n - 1 - i))] = random.randint(0, order - 1) - return c + GF(z) - -def export(ct): - output = "ct = [ " + ", ".join([str(int(cell)) for cell in ct]) + " ]" - with open("message.py", "w") as f: - f.write(output) - f.write("\ndef get():\n\treturn ct") - -if __name__ == "__main__": - main() diff --git a/app_local/generate.py b/app_local/generate.py deleted file mode 100644 index 1732478..0000000 --- a/app_local/generate.py +++ /dev/null @@ -1,58 +0,0 @@ -import numpy as np -import galois -import random - -n = 127 -k = 32 -order = 2 ** 7 -GF = galois.GF(order) - -def main(): - S = generate_S() - G = generate_G() - P = generate_P() - G_ = S @ G @ P - export_pubkey(G_) - export_privkey(S, P) - print("Done!") - -def generate_S(): - S = GF.Random((k, k)) - while np.linalg.det(S) == 0: - S = GF.Random((k, k)) - return S - -def generate_G(): - rs = galois.ReedSolomon(n, k, field=GF) - G = rs.G - return G - -def generate_P(): - r = [i for i in range(n)] - p = [] - for i in range(n): - p.append(r.pop(random.randint(0, n - 1 - i))) - P = GF.Zeros((n, n)) - for i in range(n): - P[i, p[i]] = 1 - return P - -def export_pubkey(G_): - rows = [", ".join([str(int(cell)) for cell in row]) for row in G_] - output = "G_ = [ " + ",\n".join([f"[{row}]" for row in rows]) + " ]" - with open("pubkey.py", "w") as f: - f.write(output) - f.write("\ndef get():\n\treturn G_") - -def export_privkey(S, P): - rows = [", ".join([str(int(cell)) for cell in row]) for row in S] - output = "S = [ " + ",\n".join([f"[{row}]" for row in rows]) + " ]\n" - rows = [", ".join([str(int(cell)) for cell in row]) for row in P] - output += "P = [ " + ",\n".join([f"[{row}]" for row in rows]) + " ]\n" - with open("privkey.py", "w") as f: - f.write(output) - f.write("\ndef get_S():\n\treturn S") - f.write("\ndef get_P():\n\treturn P") - -if __name__ == "__main__": - main() diff --git a/app_local/icon.ico b/app_local/icon.ico new file mode 100644 index 0000000..c7a31c2 Binary files /dev/null and b/app_local/icon.ico differ diff --git a/app_local/portable.py b/app_local/old_portable.py similarity index 100% rename from app_local/portable.py rename to app_local/old_portable.py diff --git a/app_local/portable_v2.py b/app_local/portable_v2.py deleted file mode 100644 index f27aaa4..0000000 --- a/app_local/portable_v2.py +++ /dev/null @@ -1,358 +0,0 @@ -#pip install pyinstaller -#pyinstaller -F -i "icon.ico" portable.py -#exe into dist folder - -import numpy as np -import galois -import random -import getpass -import base64 - -def main(): - safe_start() - -def safe_start(): - try: - start_menu() - except: - print("\nUnknown error (maybe ctrl+c), emergency exit!") - -def start_menu(): - f = True - print("\nA soldering iron is into a black hole.") - #thermorectal cryptanalysis - if myhash(getpass.getpass("Login: ")) != 1314399736851798576: - f = False - if myhash(getpass.getpass("Password: ")) != 192441972608755898: - f = False - if f: - print("Authorization successful, wait a bit.") - menu() - else: - print("Permission denied.") - print("\nPress ENTER to exit.", end = '') - input() - -def menu(): - order = 2 ** 8 # = p^m = 256 = byte size, we well encrypt each byte and save in base64 format - n = order - 1 # = 255, (order - 1) mod n = 0 for Reed Solomon code (below i will name it RScode), n mod 3 = 0 for base64 - k = 210 #k mod 3 = 0 for base64, n >= k, RScode can correct (n - k) // 2 errors in message - # - #print(galois.GF(2 ** 8).properties) to know irreducible_poly and primitive_element (pyinstaller will not add database files) - #example of path to *.db files is C:\Python_3_12_2\Lib\site-packages\galois\_databases and _interface.py (which opens the database) is also here - #maybe it is possible to use --add-data pyinstaller option, but I didn't figure out which paths to write so that galois could find the database - #you can write a function that changes the configuration of the cryptosystem, it is easy to change n and k and rebuild RScode during execution - #and if you understand how to add database files using Pyinstaller, you can change the order during portable execution too - #you can even use order = 2**7 (or any from 128 to 255 which is p^m where p is prime, m is natural) for ascii coding or make your own alphabet table - #but if you want to change the order, you will have to give up the wonderful storage of keys and messages in base64 format... - #...and use the first version of this program for saving raw integers (or reduce the order and abandon universal work for any encoding) - # - #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - #_interface.py causes an error during portable execution if it cannot find the database, but there is a hint at the end of this file: - #Alternatively, you can find irreducible polynomials with galois.irreducible_poly(p, m) or primitive polynomials with galois.primitive_poly(p, m). - #maybe it the key to the solution of the database problem (as I understand, these functions don't use database), check it; and maybe verify = True - #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # - #nothing from the big comment above is needed if you don't want to build portable exe, just GF = galois.GF(order) - # - GF = galois.GF(2, 8, irreducible_poly = "x^8 + x^4 + x^3 + x^2 + 1", primitive_element = "x", verify = False) - rs = galois.ReedSolomon(n, k, field = GF) - print("\nMcEliece cryptosystem implementation by vovuas2003.\n") - print("All necessary txt files must be located in the directory with this exe program.\n") - info = "Menu numbers: 0 = exit, 1 = generate keys, 2 = encrypt, 3 = decrypt,\n4 = restore pubkey, 5 = break privkey_s, 6 = break privkey_p; h = help.\n" - err = "Error! Check command info and try again!\n" - ok = "Operation successful.\n" - inp = [str(i) for i in range(7)] + ['h'] + ['1337'] - print(info) - while True: - s = input("Menu number: ") - while s not in inp: - s = input("Wrong menu number, h = help: ") - if s == 'h': - print(info) - elif s == '0': - print("\nGood luck!") - break - elif s == '1': - print("This operation will rewrite pubkey.txt, privkey_s.txt and privkey_p.txt; are you sure?") - if(not get_yes_no()): - continue - try: - generate(n, k, GF, rs) - print(ok) - except: - print(err) - elif s == '2': - print("Write your text into text.txt; pubkey.txt is required, message.txt will be rewritten.") - if(not get_yes_no()): - continue - try: - encrypt(n, k, order, GF) - print(ok) - except: - print(err) - elif s == '3': - print("You need message.txt, privkey_s.txt and privkey_p.txt; text.txt will be rewritten.") - if(not get_yes_no()): - continue - try: - decrypt(n, k, GF, rs) - print(ok) - except: - print(err) - elif s == '4': - print("You need privkey_s.txt and privkey_p.txt; pubkey.txt will be rewritten.") - if(not get_yes_no()): - continue - try: - restore_G_(n, k, GF, rs) - print(ok) - except: - print(err) - elif s == '5': - print("You need pubkey.txt and privkey_p.txt; privkey_s.txt will be rewritten.") - if(not get_yes_no()): - continue - try: - break_S(n, k, GF) - print(ok) - except: - print(err) - elif s == '6': - print("You need pubkey.txt and privkey_s.txt; privkey_p.txt will be rewritten.") - if(not get_yes_no()): - continue - try: - break_P(n, k, GF, rs) - print(ok) - except: - print(err) - elif s == '1337': - c = input("Move the soldering iron into the black hole number: ") - try: - PT(int(c)) - except: - print("Iron: 'I don't know this hole.'") - continue - else: - print("Impossible behaviour, mistake in source code!\nThe string allowed in the inp array is not bound to the call of any function!") - break - -def get_yes_no(): - s = input("Confirm (0 = go back, 1 = continue): ") - while s not in ['0', '1']: - s = input("Try again, 0 or 1: ") - return int(s) - -def myhash(s, m = 2**61 - 1, p = 257): - a = 0 - for i in range(len(s)): - a = ((a * p) % m + ord(s[i])) % m - return a - -def PT(m): - M = 5 - if m == 0: - print("Iron: 'OK, I will choose the number by myself.'") - while m == 0: - m = random.randint(-M, M) - s = "PT!" - p = " " - f = False - if m < 0: - s, p = p, s - m *= -1 - f = True - if m > M: - print("Iron: 'Are you sure to move me so far?'") - if(not get_yes_no()): - return - print() - if f: - print(p * (10 * m + 1)) - print(p + (s * 3 + p + s * 3 + p + s + p) * m) - print(p + (s + p + s + p * 2 + s + p * 2 + s + p) * m) - print(p + (s * 3 + p * 2 + s + p * 2 + s + p) * m) - print(p + (s + p * 4 + s + p * 4) * m) - print(p + (s + p * 4 + s + p * 2 + s + p) * m) - if f: - print(p * (10 * m + 1)) - print() - -def generate(n, k, GF, rs): - S = generate_S(k, GF) - G = rs.G - P, p = generate_P(n, GF) - G_ = S @ G @ P - write_pubkey(G_) - write_privkey_s(S) - write_privkey_p(p) - -def generate_S(k, GF): - S = GF.Random((k, k)) - while np.linalg.det(S) == 0: - S = GF.Random((k, k)) - return S - -def generate_P(n, GF): - r = [i for i in range(n)] - p = [] - for i in range(n): - p.append(r.pop(random.randint(0, n - 1 - i))) - P = GF.Zeros((n, n)) - for i in range(n): - P[i, p[i]] = 1 - return P, p - -def write_pubkey(G_): - rows = [bytes(row) for row in G_] - output = "".join([base64.b64encode(row).decode() for row in rows]) - with open("pubkey.txt", "w") as f: - f.write(output) - -def write_privkey_s(S): - rows = [bytes(row) for row in S] - output = "".join([base64.b64encode(row).decode() for row in rows]) - with open("privkey_s.txt", "w") as f: - f.write(output) - -def write_privkey_p(p): - output = base64.b64encode(bytes(p)).decode() - with open("privkey_p.txt", "w") as f: - f.write(output) - -def read_pubkey(n, k): - with open("pubkey.txt", "r") as f: - out = f.read() - out = [int(i) for i in base64.b64decode(out)] - out = [out[i - n : i] for i in range(n, n * k + n, n)] - return out - -def read_privkey_s(k): - with open("privkey_s.txt", "r") as f: - out = f.read() - out = [int(i) for i in base64.b64decode(out)] - out = [out[i - k : i] for i in range(k, k * k + k, k)] - return out - -def read_privkey_p(): - with open("privkey_p.txt", "r") as f: - out = f.read() - return [int(i) for i in base64.b64decode(out)] - -def build_P(n, GF, p): - P = GF.Zeros((n, n)) - for i in range(n): - P[i, p[i]] = 1 - return P - -def build_P_inv(n, GF, p): - P = GF.Zeros((n, n)) - for i in range(n): - P[p[i], i] = 1 - return P - -def pad_message(msg: bytes, pad_size: int) -> list[int]: - padding = pad_size - (len(msg) % pad_size) - return list(msg + padding.to_bytes() * padding) - -def unpad_message(msg): - padding_byte = msg[-1] - for i in range(1, padding_byte + 1): - if msg[-i] != padding_byte: - print("Wrong privkey!") - raise Exception() - return msg[: -padding_byte] - -def encrypt(n, k, order, GF): - G_ = GF(read_pubkey(n, k)) - with open("text.txt", "r") as f: - text = f.read() - text = text.encode() - out = "" - while len(text) > k - 1: - tmp = text[: k - 1] - text = text[k - 1 :] - out += encrypt_one(n, k, order, GF, G_, tmp) - out += encrypt_one(n, k, order, GF, G_, text) - with open("message.txt", "w") as f: - f.write(out) - -def encrypt_one(n, k, order, GF, G_, text): - msg = pad_message(text, k) - m = GF(msg) - c = m.T @ G_ - t = (n - k) // 2 - z = np.zeros(n, dtype = int) - p = [i for i in range(n)] - for i in range(t): - ind = p.pop(random.randint(0, n - 1 - i)) - z[ind] += random.randint(1, order - 1) - z[ind] %= order - c = c + GF(z) - return base64.b64encode(bytes(c)).decode() - -def decrypt(n, k, GF, rs): - S_inv = np.linalg.inv(GF(read_privkey_s(k))) - P_inv = GF(build_P_inv(n, GF, read_privkey_p())) - with open("message.txt", "r") as f: - msg = f.read() - msg = [int(i) for i in base64.b64decode(msg)] - msg = [msg[i - n : i] for i in range(n, len(msg) + n, n)] - msg = [decrypt_one(rs, S_inv, P_inv, GF(i)) for i in msg] - msg = [i for j in msg for i in j] - msg = bytes(msg).decode() - with open("text.txt", "w") as f: - f.write(msg) - -def decrypt_one(rs, S_inv, P_inv, msg): - msg = msg @ P_inv - msg, e = rs.decode(msg, errors = True) - if e == -1: - print("Too many erroneous values in message!") - raise Exception() - msg = msg @ S_inv - msg = [int(i) for i in msg] - msg = unpad_message(msg) - return msg - -def restore_G_(n, k, GF, rs): - S = GF(read_privkey_s(k)) - G = rs.G - P = GF(build_P(n, GF, read_privkey_p())) - G_ = S @ G @ P - write_pubkey(G_) - -def break_S(n, k, GF): - G_ = GF(read_pubkey(n, k)) - P_inv = GF(build_P_inv(n, GF, read_privkey_p())) - S = G_ @ P_inv - S = S[:, : k] - write_privkey_s(S) - -def break_P(n, k, GF, rs): - G_ = GF(read_pubkey(n, k)) - S_inv = np.linalg.inv(GF(read_privkey_s(k))) - G = rs.G - G = G.T - G = [[int(i) for i in j] for j in G] - GP = S_inv @ G_ - GP = GP.T - GP = [[int(i) for i in j] for j in GP] - p = [0 for i in range(n)] - f = False - for i in range(n): - f = False - for j in range(n): - if G[i] == GP[j]: - p[i] = j - f = True - break - if f: - continue - print("Wrong pubkey and privkey_s combination!") - raise Exception() - write_privkey_p(p) - -if __name__ == "__main__": - main() diff --git a/app_local/portable_v3_gui.py b/app_local/portable_v3_gui.py deleted file mode 100644 index 878141c..0000000 --- a/app_local/portable_v3_gui.py +++ /dev/null @@ -1,110 +0,0 @@ -#G = pubkey; S and P = privkeys; text = plaintext; msg = encrypted text -#all these variables are strings -#Usage: -#G, S, P = generate() -#msg = encrypt(G, text) -#text = decrypt(S, P, msg) -#G = restore_G(S, P) -#S = break_S(G, P) -#P = break_P(G, S) - -import portable_v3_core as core -import tkinter as tk - -def button1_click(): - new_text1, new_text2, new_text3 = core.generate() - entry1.delete(0, tk.END) - entry1.insert(0, new_text1) - entry2.delete(0, tk.END) - entry2.insert(0, new_text2) - entry3.delete(0, tk.END) - entry3.insert(0, new_text3) - -def button2_click(): - G = entry1.get() - text = entry4.get("1.0", "end-1c") - - # Выполнить какие-либо действия с текстом - new_text4 = core.encrypt(G, text) - - # Записать текст обратно в поля ввода - entry4.delete("1.0", tk.END) - entry4.insert("1.0", new_text4) - -def button3_click(): - S = entry2.get() - P = entry3.get() - msg = entry4.get("1.0", "end-1c") - - # Выполнить какие-либо действия с текстом - new_text4 = core.decrypt(S, P, msg) - - # Записать текст обратно в поля ввода - entry4.delete("1.0", tk.END) - entry4.insert("1.0", new_text4) - -def button4_click(): - S = entry2.get() - P = entry3.get() - - # Выполнить какие-либо действия с текстом - new_text1 = core.restore_G(S, P) - - # Записать текст обратно в поля ввода - entry1.delete(0, tk.END) - entry1.insert(0, new_text1) - -def button5_click(): - G = entry1.get() - P = entry3.get() - - # Выполнить какие-либо действия с текстом - new_text2 = core.break_S(G, P) - - # Записать текст обратно в поля ввода - entry2.delete(0, tk.END) - entry2.insert(0, new_text2) - -def button6_click(): - G = entry1.get() - S = entry2.get() - - # Выполнить какие-либо действия с текстом - new_text3 = core.break_P(G, S) - - # Записать текст обратно в поля ввода - entry3.delete(0, tk.END) - entry3.insert(0, new_text3) - -# Создать главное окно -window = tk.Tk() -window.title("McEliece by vovuas2003") - -# Создать поля для ввода текста -entry1 = tk.Entry(window, width=50) -entry2 = tk.Entry(window, width=50) -entry3 = tk.Entry(window, width=50) -entry4 = tk.Text(window, height=10, width=50) - -# Создать кнопки -button1 = tk.Button(window, text="generate", command=button1_click) -button2 = tk.Button(window, text="encrypt", command=button2_click) -button3 = tk.Button(window, text="decrypt", command=button3_click) -button4 = tk.Button(window, text="pubkey", command=button4_click) -button5 = tk.Button(window, text="privkey_s", command=button5_click) -button6 = tk.Button(window, text="privkey_p", command=button6_click) - -# Разместить элементы в окне -entry1.pack() -entry2.pack() -entry3.pack() -entry4.pack(fill=tk.BOTH, expand=True) -button1.pack() -button2.pack() -button3.pack() -button4.pack() -button5.pack() -button6.pack() - -# Запустить главное окно -window.mainloop() diff --git a/app_local/readme.txt b/app_local/readme.txt index 719830f..6ba3c73 100644 --- a/app_local/readme.txt +++ b/app_local/readme.txt @@ -1,21 +1,13 @@ McEliece cryptosystem implementation by vovuas2003 -Update: portable version is available! All functions in one file. New features and some improvements! -Update 2: portable_v2 is available! Nice keys and message saving in base64 string format, so it is easy to integrate with other projects by a small change of read and write functions. +Required Python libraries: numpy, galois. -Instruction for old version below -Usage: -0. pip install numpy and galois -1. generate.py - generate and save public and private keys -2. send pubkey.py and encode.py to your friend -3. your friend runs encode.py, write secret string and send message.py to you -4. decode.py - get secret string +All cryptosystem functions are implemented in cryptosystem_core.py, just import it into your project and enjoy! +For example, I coded a console menu (that works with txt files) and a GUI app. -Hacker can get your private key if he will know a half of it (and pubkey.py, decode.py and Reed-Solomon algo). -Check break.py to understand how hacker can do this. +It is possible to build portable exe with pyinstaller and run code on a computer that does not have Python installed. +But it is NOT compilation, so exe file will be quite large. -todo: -0. DONE!(in portable versions) build portable exe with pyinstaller -1. left part of G is E, because we use Reed-Solomon algo; so left part of S @ G is S and cutting right colomns works; my_fix(G) returns E and in break_S we needn't get inv(G), just S = my_fix(G_ @ inv(P)); try break_S with another (not Reed-Solomon) code (matrix G will be different; will my_fix(G) and my_fix(G_) return nonsingular matrices?; of course, rank(G) = rank(G_) = k and we can iterate through all possible combinations of column deletions and find one that does not lead to nonsingular matrices); another way to get S is calculating it row by row (solving k systems, each has n equations with k variables, k < n, but we need to do it in Galois Field) -2. DONE! check randomization during encode (add vector z, check https://en.wikipedia.org/wiki/McEliece_cryptosystem) -3. DONE!(Russian language) make presentation that explains McEliece cryptosystem +The pdf presentation in Russian contains a bit of theory about the Mceliece cryptosystem. +icon.ico is an optional file for pyinstaller +old_portable.py can support another order of galois field, but saves raw integers and does not specify utf-8 encoding for strings and txt files \ No newline at end of file