From f58c99f231e7dd905a9130affc0c7c663555ace7 Mon Sep 17 00:00:00 2001 From: Naumkin Vladimir Date: Tue, 12 Nov 2024 00:16:32 +0300 Subject: [PATCH] some improvements --- app_local/McEliece_console_v2.py | 21 +- app_local/cryptosystem_core_v2.py | 28 +- .../{ => legacy and experiments}/McEliece.pdf | Bin .../McEliece_GUI.py | 0 .../McEliece_console.py | 0 .../cryptosystem_core.py | 0 .../legacy and experiments/fast_password.py | 92 +++++++ .../{ => legacy and experiments}/icon.ico | Bin .../old_portable.py | 0 app_local/legacy and experiments/password.py | 249 ++++++++++++++++++ app_local/readme.txt | 14 +- 11 files changed, 377 insertions(+), 27 deletions(-) rename app_local/{ => legacy and experiments}/McEliece.pdf (100%) rename app_local/{ => legacy and experiments}/McEliece_GUI.py (100%) rename app_local/{ => legacy and experiments}/McEliece_console.py (100%) rename app_local/{ => legacy and experiments}/cryptosystem_core.py (100%) create mode 100644 app_local/legacy and experiments/fast_password.py rename app_local/{ => legacy and experiments}/icon.ico (100%) rename app_local/{ => legacy and experiments}/old_portable.py (100%) create mode 100644 app_local/legacy and experiments/password.py diff --git a/app_local/McEliece_console_v2.py b/app_local/McEliece_console_v2.py index cb8b33a..11835e0 100644 --- a/app_local/McEliece_console_v2.py +++ b/app_local/McEliece_console_v2.py @@ -2,6 +2,7 @@ import hashlib import getpass +import pyperclip def main(): safe_start() @@ -32,11 +33,11 @@ def menu(): import cryptosystem_core_v2 as ME_core core = ME_core.McEliece_core() global_info = "All files are interpreted as raw bytes and must be located in the directory with this executable file.\nDefault filenames with .bin extension: pubkey, privkey_S, privkey_p, plaintext, ciphertext, ciphered_string.\nDon't forget to import (or generate) keys before encryption/decryption and after changing config!\nYou can restore any one key from two another (don't forget to import before).\n" - info = "Menu numbers: 0 = exit, s = print short info, h = print this info, g = print global info, c = change config;\n1 = generate keys, 10 = unsafe generate keys (seed = hash(password)),\n11 = export pubkey, 12 = export privkey_S, 13 = export privkey_p,\n14 = import pubkey, 15 = import privkey_S, 16 = import privkey_p,\n17 = restore pubkey, 18 = restore privkey_S, 19 = restore privkey_p;\n2 = encrypt,\n21 = encrypt non-default filename, 22 = encrypt string from keyboard, 23 = encrypt hided string.\n3 = decrypt,\n31 = decrypt non-default filename, 32 = decrypt string and show on screen\n" + info = "Menu numbers: 0 = exit, s = print short info, h = print this info, g = print global info, c = change config;\n1 = generate keys, 10 = unsafe generate keys (seed = hash(password));\n11 = export pubkey, 12 = export privkey_S, 13 = export privkey_p,\n14 = import pubkey, 15 = import privkey_S, 16 = import privkey_p,\n17 = restore pubkey, 18 = restore privkey_S, 19 = restore privkey_p;\n2 = encrypt,\n21 = encrypt non-default filename, 22 = encrypt string from keyboard, 23 = encrypt hided string;\n3 = decrypt,\n31 = decrypt non-default filename, 32 = decrypt string on screen, 33 = decrypt string to clipboard.\n" short_info = "0 = exit, s/h/g = print short/extended/global info, c = change config;\n1 = generate keys, 2 = encrypt, 3 = decrypt\n" err = "Error! Check global info (menu number g) and try again!\n" ok = "Operation successful.\n" - inp = ['0', 's', 'h', 'g', 'c'] + [str(i) for i in range(1, 4)] + [str(i) for i in range(10, 20)] + [str(i) for i in range(21, 24)] + [str(i) for i in range(31, 33)] + ['1337'] + inp = ['0', 's', 'h', 'g', 'c'] + [str(i) for i in range(1, 4)] + [str(i) for i in range(10, 20)] + [str(i) for i in range(21, 24)] + [str(i) for i in range(31, 34)] + ['1337'] print("\nMcEliece cryptosystem implementation by vovuas2003. Version 2.\n") print(global_info) print(info) @@ -225,7 +226,7 @@ def menu(): except: print(err) elif s == '32': - print("Need ciphered_string.bin!") + print("Need ciphered_string.bin! Visible input!!!") if(not get_yes_no()): continue try: @@ -233,6 +234,20 @@ def menu(): print(ok) except: print(err) + elif s == '33': + print("Need ciphered_string.bin! Copy to clipboard.") + if(not get_yes_no()): + continue + try: + tmp = bytes(core.decrypt([int(i) for i in read_file("ciphered_string.bin")])).decode('utf-8') + except: + print(err) + continue + try: + pyperclip.copy(tmp) + print(ok) + except: + print("Decryption was successful, but program can't use clipboard.\nOn Linux (especially Ubuntu) try to install xclip (e.g. via apt-get).") elif s == '1337': PT() else: diff --git a/app_local/cryptosystem_core_v2.py b/app_local/cryptosystem_core_v2.py index eaf3070..8b00ecc 100644 --- a/app_local/cryptosystem_core_v2.py +++ b/app_local/cryptosystem_core_v2.py @@ -65,7 +65,8 @@ class McEliece_core: def __init__(self): self._order = 256 #p^m = 2**8; encryption of each byte self._n = 255 #(order - 1) mod n = 0 for Reed Solomon code; 255 = 3 * 5 * 17 = (order - 1) - self._k = 210 #2 <= k <= n; randomly change (n - k) div 2 bytes during encryption, but add (n - k + 1) bytes to each chunk with len (k - 1); k != 1 for padding function; k almost equal to n is very bad because of small amount of randomly changed bytes (k == n -> privkey for decryption == numpy.linalg.inv(pubkey)) + self._k = 210 #2 <= k <= n; randomly change t = (n - k) div 2 bytes during encryption, but add (n - k + 1) bytes to each chunk with len (k - 1); k != 1 for padding function; k almost equal to n is very bad because of small amount of randomly changed bytes (k == n -> privkey for decryption == numpy.linalg.inv(pubkey)) + self._t = (self._n - self._k) // 2 self._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 self._rs = galois.ReedSolomon(self._n, self._k, field = self._GF) self._G = self._GF.Zeros((self._k, self._n)) #pubkey @@ -80,10 +81,11 @@ class McEliece_core: raise Exception() rs = galois.ReedSolomon(n, k, field = self._GF) except: - raise Exception() + raise else: self._n = n self._k = k + self._t = (n - k) // 2 self._rs = rs #Also unset all keys! self._G = self._GF.Zeros((self._k, self._n)) @@ -101,8 +103,9 @@ class McEliece_core: elif type(seed) != int: raise Exception() else: - self._unsafe_generate_S(seed % (2**32)) - self._unsafe_generate_P(seed % (2**32)) + seed %= 2**32 + self._unsafe_generate_S(seed) + self._unsafe_generate_P(seed) self._G = self._S @ self._rs.G @ self._P def get_pubkey(self): return [int(i) for j in self._G for i in j] @@ -115,7 +118,7 @@ class McEliece_core: G = [G[i - self._n : i] for i in range(self._n, self._n * self._k + self._n, self._n)] G = self._GF(G) except: - raise Exception() + raise else: self._G = G def set_privkey_S(self, S): @@ -124,7 +127,7 @@ class McEliece_core: S = self._GF(S) S_inv = np.linalg.inv(S) except: - raise Exception() + raise else: self._S = S self._S_inv = S_inv @@ -146,7 +149,7 @@ class McEliece_core: try: S_inv = np.linalg.inv(S) except: - raise Exception() + raise self._S = S self._S_inv = S_inv def restore_privkey_p(self): @@ -168,6 +171,8 @@ class McEliece_core: if f: continue raise Exception() + if sorted(p) != [i for i in range(self._n)]: + raise Exception() self._p = p self._P = self._GF.Zeros((self._n, self._n)) self._P_inv = self._GF.Zeros((self._n, self._n)) @@ -184,14 +189,14 @@ class McEliece_core: out += self._encrypt_one(text) return out except: - raise Exception() + raise def decrypt(self, msg): try: msg = [msg[i - self._n : i] for i in range(self._n, len(msg) + self._n, self._n)] msg = [self._decrypt_one(self._GF(i)) for i in msg] return [i for j in msg for i in j] except: - raise Exception() + raise #End of top-level functions, please do NOT use functions below without understanding! def _generate_S(self): S = self._GF.Random((self._k, self._k)) @@ -231,10 +236,9 @@ class McEliece_core: msg = self._pad_message(text) m = self._GF(msg) c = m.T @ self._G - t = (self._n - self._k) // 2 z = np.zeros(self._n, dtype = int) p = [i for i in range(self._n)] - for i in range(t): + for i in range(self._t): ind = p.pop(random.randint(0, self._n - 1 - i)) z[ind] = random.randint(1, self._order - 1) c = c + self._GF(z) @@ -249,7 +253,7 @@ class McEliece_core: try: msg = self._unpad_message(msg) except: - raise Exception() + raise return msg def _pad_message(self, msg): last_value = self._k - (len(msg) % self._k) diff --git a/app_local/McEliece.pdf b/app_local/legacy and experiments/McEliece.pdf similarity index 100% rename from app_local/McEliece.pdf rename to app_local/legacy and experiments/McEliece.pdf diff --git a/app_local/McEliece_GUI.py b/app_local/legacy and experiments/McEliece_GUI.py similarity index 100% rename from app_local/McEliece_GUI.py rename to app_local/legacy and experiments/McEliece_GUI.py diff --git a/app_local/McEliece_console.py b/app_local/legacy and experiments/McEliece_console.py similarity index 100% rename from app_local/McEliece_console.py rename to app_local/legacy and experiments/McEliece_console.py diff --git a/app_local/cryptosystem_core.py b/app_local/legacy and experiments/cryptosystem_core.py similarity index 100% rename from app_local/cryptosystem_core.py rename to app_local/legacy and experiments/cryptosystem_core.py diff --git a/app_local/legacy and experiments/fast_password.py b/app_local/legacy and experiments/fast_password.py new file mode 100644 index 0000000..07dd449 --- /dev/null +++ b/app_local/legacy and experiments/fast_password.py @@ -0,0 +1,92 @@ +import numpy as np +import galois +import random +import getpass +import hashlib +import pyperclip + +def main(): + try: + menu() + except: + print("Error!") + +def menu(): + core = McEliece_core() + core.generate_keys(normalhash(getpass.getpass("Password: "))) + pyperclip.copy(bytes(core.decrypt([int(i) for i in read_file("only_password")])).decode('utf-8')) + print("OK!") + +class McEliece_core: + def __init__(self): + self._order = 256 + self._n = 255 + self._k = 210 + self._GF = galois.GF(2, 8, irreducible_poly = "x^8 + x^4 + x^3 + x^2 + 1", primitive_element = "x", verify = False) + self._rs = galois.ReedSolomon(self._n, self._k, field = self._GF) + self._G = self._GF.Zeros((self._k, self._n)) + self._S = self._GF.Zeros((self._k, self._k)) + self._S_inv = self._GF.Zeros((self._k, self._k)) + self._P = self._GF.Zeros((self._n, self._n)) + self._P_inv = self._GF.Zeros((self._n, self._n)) + self._p = [0 for i in range(self._n)] + def generate_keys(self, seed): + seed %= 2**32 + self._unsafe_generate_S(seed) + self._unsafe_generate_P(seed) + self._G = self._S @ self._rs.G @ self._P + def decrypt(self, msg): + try: + msg = [msg[i - self._n : i] for i in range(self._n, len(msg) + self._n, self._n)] + msg = [self._decrypt_one(self._GF(i)) for i in msg] + return [i for j in msg for i in j] + except: + raise + def _unsafe_generate_S(self, seed): + pseudo = np.random.RandomState(seed) + S = self._GF(pseudo.randint(0, self._order, (self._k, self._k))) + while np.linalg.det(S) == 0: + S = self._GF(pseudo.randint(0, self._order, (self._k, self._k))) + self._S = S + self._S_inv = np.linalg.inv(S) + def _unsafe_generate_P(self, seed): + pseudo = np.random.RandomState(seed) + p = [i for i in range(self._n)] + pseudo.shuffle(p) + self._p = p + self._P = self._GF.Zeros((self._n, self._n)) + self._P_inv = self._GF.Zeros((self._n, self._n)) + for i in range(self._n): + self._P[i, p[i]] = 1 + self._P_inv[p[i], i] = 1 + def _decrypt_one(self, msg): + msg = msg @ self._P_inv + msg, e = self._rs.decode(msg, errors = True) + if e == -1: + raise Exception() + msg = msg @ self._S_inv + msg = [int(i) for i in msg] + try: + msg = self._unpad_message(msg) + except: + raise + return msg + def _unpad_message(self, msg): + last_value = msg[-1] + if last_value >= self._k or last_value <= 0: + raise Exception() + for i in range(1, last_value + 1): + if msg[-i] != last_value: + raise Exception() + return msg[: -last_value] + +def read_file(name): + with open(name, "rb") as f: + data = f.read() + return data + +def normalhash(s): + return int(hashlib.sha256(bytearray(s, 'utf-8')).hexdigest(), 16) + +if __name__ == "__main__": + main() diff --git a/app_local/icon.ico b/app_local/legacy and experiments/icon.ico similarity index 100% rename from app_local/icon.ico rename to app_local/legacy and experiments/icon.ico diff --git a/app_local/old_portable.py b/app_local/legacy and experiments/old_portable.py similarity index 100% rename from app_local/old_portable.py rename to app_local/legacy and experiments/old_portable.py diff --git a/app_local/legacy and experiments/password.py b/app_local/legacy and experiments/password.py new file mode 100644 index 0000000..8fe4876 --- /dev/null +++ b/app_local/legacy and experiments/password.py @@ -0,0 +1,249 @@ +import numpy as np +import galois +import random +import getpass +import hashlib +import pyperclip + +def main(): + try: + menu() + except: + print("\nUnknown error (maybe ctrl+c), emergency exit!") + +def menu(): + info = "Menu numbers: h = info, t = test clipboard, c = config;\n0 = exit, 1 = generate, 2 = encrypt, 3 = decrypt\n" + err = "Error, try again!\n" + ok = "Operation successful.\n" + inp = ['h', 'c', 't'] + [str(i) for i in range (4)] + ['1337'] + core = McEliece_core() + print("\nMcEliece password encryption by vovuas2003.\n") + print(info) + while True: + s = input("Menu number: ") + while s not in inp: + s = input("Wrong menu number; h = help: ") + if s == '0': + print("\nGood luck!") + break + elif s == 'h': + print(info) + elif s == 't': + print("Do you want to check Python ability to use clipboard in your OS?") + if(not get_yes_no()): + continue + try: + print('Trying to write "Hello, world!" to clipboard.') + mycopy("Hello, world!") + print("Success! You can check ctrl+v.") + except: + print("Python cannot use clipboard. On Linux maybe you need to run command: sudo apt-get install xclip.\nOr try code below to check python error log:\nimport pyperclip #don't forget to pip install pyperclip\npyperclip.copy(\"Hello, world!\")") + elif s == 'c': + n, k = core.get_config() + print("Default config is 255 210, current is " + str(n) + " " + str(k) + ". Change config? Also reset all keys!") + if(not get_yes_no()): + continue + try: + print("Config is two numbers n >= k >= 2; (3 * 5 * 17) mod n = 0. Larger values = larger keys.\nRandomly change (n - k) div 2 bytes during encryption, but add (n - k + 1) bytes to each chunk with len (k - 1).") + n, k = map(int, input("Write n and k separated by a space: ").split()) + core.change_config(n, k) + print(ok) + except: + print(err) + elif s == '1': + print("Reset all keys!") + if(not get_yes_no()): + continue + try: + core.generate_keys(normalhash(getpass.getpass("Password for hash: "))) + print(ok) + except: + print(err) + elif s == '2': + print("Encrypt hided input (ONLY ONE LINE!!!) from clipboard!") + if(not get_yes_no()): + continue + try: + inp_str = getpass.getpass("Hided string to encrypt: ") + out_name = input("Filename to save: ") + if not out_name: + print("Using default name ciphered_string.bin") + out_name = "ciphered_string.bin" + write_file(out_name, bytes(core.encrypt([int(i) for i in inp_str.encode('utf-8')]))) + print(ok) + except: + print(err) + elif s == '3': + print("Decrypt string from file and copy to clipboard!") + if(not get_yes_no()): + continue + try: + inp_name = input("Filename to decrypt: ") + if not inp_name: + print("Using default name ciphered_string.bin") + inp_name = "ciphered_string.bin" + mycopy(bytes(core.decrypt([int(i) for i in read_file(inp_name)])).decode('utf-8')) + print(ok) + except: + print(err) + elif s == '1337': + mycopy(PT()) + else: + print("Impossible behaviour, mistake in source code, emergency exit!\nThe string allowed in the inp array is not bound to the call of any function!") + break + +class McEliece_core: + def __init__(self): + self._order = 256 + self._n = 255 + self._k = 210 + self._t = (self._n - self._k) // 2 + self._GF = galois.GF(2, 8, irreducible_poly = "x^8 + x^4 + x^3 + x^2 + 1", primitive_element = "x", verify = False) + self._rs = galois.ReedSolomon(self._n, self._k, field = self._GF) + self._G = self._GF.Zeros((self._k, self._n)) + self._S = self._GF.Zeros((self._k, self._k)) + self._S_inv = self._GF.Zeros((self._k, self._k)) + self._P = self._GF.Zeros((self._n, self._n)) + self._P_inv = self._GF.Zeros((self._n, self._n)) + self._p = [0 for i in range(self._n)] + def change_config(self, n, k): + try: + if k < 2: + raise Exception() + rs = galois.ReedSolomon(n, k, field = self._GF) + except: + raise + else: + self._n = n + self._k = k + self._t = (n - k) // 2 + self._rs = rs + self._G = self._GF.Zeros((self._k, self._n)) + self._S = self._GF.Zeros((self._k, self._k)) + self._S_inv = self._GF.Zeros((self._k, self._k)) + self._P = self._GF.Zeros((self._n, self._n)) + self._P_inv = self._GF.Zeros((self._n, self._n)) + self._p = [0 for i in range(self._n)] + def get_config(self): + return self._n, self._k + def generate_keys(self, seed): + seed %= 2**32 + self._unsafe_generate_S(seed) + self._unsafe_generate_P(seed) + self._G = self._S @ self._rs.G @ self._P + def encrypt(self, text): + try: + out = [] + while len(text) > self._k - 1: + tmp = text[: self._k - 1] + text = text[self._k - 1 :] + out += self._encrypt_one(tmp) + out += self._encrypt_one(text) + return out + except: + raise + def decrypt(self, msg): + try: + msg = [msg[i - self._n : i] for i in range(self._n, len(msg) + self._n, self._n)] + msg = [self._decrypt_one(self._GF(i)) for i in msg] + return [i for j in msg for i in j] + except: + raise + def _unsafe_generate_S(self, seed): + pseudo = np.random.RandomState(seed) + S = self._GF(pseudo.randint(0, self._order, (self._k, self._k))) + while np.linalg.det(S) == 0: + S = self._GF(pseudo.randint(0, self._order, (self._k, self._k))) + self._S = S + self._S_inv = np.linalg.inv(S) + def _unsafe_generate_P(self, seed): + pseudo = np.random.RandomState(seed) + p = [i for i in range(self._n)] + pseudo.shuffle(p) + self._p = p + self._P = self._GF.Zeros((self._n, self._n)) + self._P_inv = self._GF.Zeros((self._n, self._n)) + for i in range(self._n): + self._P[i, p[i]] = 1 + self._P_inv[p[i], i] = 1 + def _encrypt_one(self, text): + msg = self._pad_message(text) + m = self._GF(msg) + c = m.T @ self._G + z = np.zeros(self._n, dtype = int) + p = [i for i in range(self._n)] + for i in range(self._t): + ind = p.pop(random.randint(0, self._n - 1 - i)) + z[ind] = random.randint(1, self._order - 1) + c = c + self._GF(z) + return [int(i) for i in c] + def _decrypt_one(self, msg): + msg = msg @ self._P_inv + msg, e = self._rs.decode(msg, errors = True) + if e == -1: + raise Exception() + msg = msg @ self._S_inv + msg = [int(i) for i in msg] + try: + msg = self._unpad_message(msg) + except: + raise + return msg + def _pad_message(self, msg): + last_value = self._k - (len(msg) % self._k) + return msg + [last_value] * last_value + def _unpad_message(self, msg): + last_value = msg[-1] + if last_value >= self._k or last_value <= 0: + raise Exception() + for i in range(1, last_value + 1): + if msg[-i] != last_value: + raise Exception() + return msg[: -last_value] + +def write_file(name, data): + with open(name, "wb") as f: + f.write(data) + +def read_file(name): + with open(name, "rb") as f: + data = f.read() + return data + +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 normalhash(s): + return int(hashlib.sha256(bytearray(s, 'utf-8')).hexdigest(), 16) + +def PT(m = -3, M = 3): + if m == 0 or abs(m) > M: + return "PT!" + s = "PT!" + p = " " + f = False + if m < 0: + s, p = p, s + m *= -1 + f = True + out = "\n" + if f: + out += p * (10 * m + 1) + "\n" + out += p + (s * 3 + p + s * 3 + p + s + p) * m + "\n" + out += p + (s + p + s + p * 2 + s + p * 2 + s + p) * m + "\n" + out += p + (s * 3 + p * 2 + s + p * 2 + s + p) * m + "\n" + out += p + (s + p * 4 + s + p * 4) * m + "\n" + out += p + (s + p * 4 + s + p * 2 + s + p) * m + "\n" + if f: + out += p * (10 * m + 1) + "\n" + out += "\n" + return out + +def mycopy(s): + pyperclip.copy(s) + +if __name__ == "__main__": + main() diff --git a/app_local/readme.txt b/app_local/readme.txt index 03272d3..45d7f04 100644 --- a/app_local/readme.txt +++ b/app_local/readme.txt @@ -1,15 +1,5 @@ McEliece cryptosystem implementation by vovuas2003 -Required Python libraries: numpy, galois. +Required additional Python libraries: numpy, galois (numpy extension). -UPDATE: version 2 is available. Check cryptosystem_core_v2 and McEliece_console_v2. - -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 text in txt files and also with any files in binary mode) and a GUI app (for text encryption). - -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. - -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 +Version 2 is the latest. Check cryptosystem_core_v2 and McEliece_console_v2.