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.

247 lines
7.3 KiB
Python

#McEliece cryptosystem implementation by vovuas2003
7 months ago
#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
7 months ago
#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()
1 month ago
G, S, P = core.unsafe_generate(seed) #e.g. seed = hash(password)
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
7 months ago
#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
7 months ago
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
7 months ago
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
7 months ago
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
1 month ago
def unsafe_generate(h):
seed = h % (2**32)
S = unsafe_generate_S(seed)
G = rs.G
P, p = unsafe_generate_P(seed)
G_ = S @ G @ P
return write_pubkey(G_), write_privkey_s(S), write_privkey_p(p)
def unsafe_generate_S(seed):
pseudo = np.random.RandomState(seed)
S = GF(pseudo.randint(0, order, (k, k)))
while np.linalg.det(S) == 0:
S = GF(pseudo.randint(0, order, (k, k)))
return S
def unsafe_generate_P(seed):
pseudo = np.random.RandomState(seed)
p = [i for i in range(n)]
pseudo.shuffle(p)
P = GF.Zeros((n, n))
for i in range(n):
P[i, p[i]] = 1
return P, p
7 months ago
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()
7 months ago
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()
7 months ago
def write_privkey_p(p):
return base64.b64encode(bytes(p)).decode()
7 months ago
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):
7 months ago
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):
7 months ago
G_ = GF(read_pubkey(key))
out = bytes()
7 months ago
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()
7 months ago
7 months ago
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
7 months ago
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)
7 months ago
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()