You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
6.6 KiB
Python

#McEliece cryptosystem implementation by vovuas2003
#Usage:
#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
#you can use bin_encrypt and bin_decrypt functions if text is a byte array (check console example)
'''
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, messages and the number of erroneous bytes in the message
#larger values = larger keys; randomly change (n - k) div 2 bytes during encryption, but add (n - k + 1) bytes to each chunk with len (k - 1).
#see the comments below to understand the requirements for n and k (or check console example)
'''
#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 #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
P, p = generate_P()
G_ = S @ G @ P
return write_pubkey(G_), write_privkey_s(S), write_privkey_p(p)
def generate_S():
S = GF.Random((k, k))
while np.linalg.det(S) == 0:
S = GF.Random((k, k))
return S
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, p
def write_pubkey(G_):
rows = [bytes(row) for row in G_]
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]
row = bytes([i for j in rows for i in j])
return base64.b64encode(row).decode()
def write_privkey_p(p):
return base64.b64encode(bytes(p)).decode()
def read_pubkey(out):
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(out):
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(out):
return [int(i) for i in base64.b64decode(out)]
def build_P(p):
P = GF.Zeros((n, n))
for i in range(n):
P[i, p[i]] = 1
return P
def build_P_inv(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(key, text):
return bin_encrypt(key, text.encode("utf-8"))
def decrypt(s, p, msg):
return bin_decrypt(s, p, msg).decode("utf-8")
def bin_encrypt(key, text):
G_ = GF(read_pubkey(key))
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 base64.b64encode(out).decode()
def bin_decrypt(s, p, msg):
S_inv = np.linalg.inv(GF(read_privkey_s(s)))
P_inv = GF(build_P_inv(read_privkey_p(p)))
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(S_inv, P_inv, GF(i)) for i in msg]
msg = [i for j in msg for i in j]
msg = bytes(msg)
return msg
def encrypt_one(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 bytes(c)
def decrypt_one(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(s, p):
S = GF(read_privkey_s(s))
G = rs.G
P = GF(build_P(read_privkey_p(p)))
G_ = S @ G @ P
return write_pubkey(G_)
def break_S(key, p):
G_ = GF(read_pubkey(key))
P_inv = GF(build_P_inv(read_privkey_p(p)))
S = G_ @ P_inv
S = S[:, : k]
return write_privkey_s(S)
def break_P(key, s):
G_ = GF(read_pubkey(key))
S_inv = np.linalg.inv(GF(read_privkey_s(s)))
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()
return write_privkey_p(p)
if __name__ == "__main__":
main()