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.
272 lines
10 KiB
Python
272 lines
10 KiB
Python
#McEliece cryptosystem implementation by vovuas2003
|
|
#v2. Power of Python OOP.
|
|
|
|
#Usage (check main function or class implementation):
|
|
#G = pubkey; S and p = privkeys; text = plaintext; msg = encrypted text.
|
|
#All these variables must be lists of integers from 0 to 255. Easy to use binary files (check console_v2).
|
|
#It is possible to rewrite my class McEliece_core and add changing the order of Galois Field (check galois lib docs) to M, so all these variables will be lists of integers from 0 to M-1.
|
|
#But it will be impossible to save keys and encrypted texts as binary files. Also you will need to add polynomial database for pyinstaller.
|
|
|
|
import numpy as np
|
|
import galois
|
|
import random
|
|
|
|
#import cryptosystem_core_v2 as ME_core
|
|
#core = ME_core.McEliece_core()
|
|
|
|
def main(): #comment "return" for testing
|
|
return
|
|
core = McEliece_core()
|
|
n, k = core.get_config()
|
|
print(n, k)
|
|
core.change_config(5, 3) #check comments in class implementation to understand possible values
|
|
n, k = core.get_config()
|
|
print(n, k)
|
|
print()
|
|
core.generate_keys() #true random
|
|
print(core.get_pubkey())
|
|
print(core.get_privkey_S())
|
|
print(core.get_privkey_p())
|
|
print()
|
|
core.generate_keys(sum([ord(i) for i in list("password")])) #very simple seed
|
|
G = core.get_pubkey()
|
|
S = core.get_privkey_S()
|
|
p = core.get_privkey_p()
|
|
print(G)
|
|
print(S)
|
|
print(p)
|
|
print()
|
|
core.change_config(5, 3) #unset all keys inside core
|
|
core.set_privkey_S(S)
|
|
core.set_privkey_p(p)
|
|
core.restore_pubkey()
|
|
print(core.get_pubkey() == G)
|
|
core.change_config(5, 3) #unset all keys inside core
|
|
core.set_pubkey(G)
|
|
core.set_privkey_p(p)
|
|
core.restore_privkey_S()
|
|
print(core.get_privkey_S() == S)
|
|
core.change_config(5, 3) #unset all keys inside core
|
|
core.set_pubkey(G)
|
|
core.set_privkey_S(S)
|
|
core.restore_privkey_p()
|
|
print(core.get_privkey_p() == p)
|
|
print()
|
|
for j in range(2):
|
|
text = [i + 1 for i in range(5)]
|
|
msg = core.encrypt(text)
|
|
print(msg)
|
|
text = core.decrypt(msg)
|
|
print(text)
|
|
print()
|
|
print("All tests are finished!")
|
|
|
|
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 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
|
|
self._S = self._GF.Zeros((self._k, self._k)) #1st part of privkey
|
|
self._S_inv = self._GF.Zeros((self._k, self._k)) #for decryption
|
|
self._P = self._GF.Zeros((self._n, self._n)) #2nd part of privkey
|
|
self._P_inv = self._GF.Zeros((self._n, self._n)) #for decryption
|
|
self._p = [0 for i in range(self._n)] #compact format of P as a permutation array
|
|
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
|
|
#Also unset all keys!
|
|
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 = None):
|
|
if seed == None:
|
|
self._generate_S()
|
|
self._generate_P()
|
|
elif type(seed) != int:
|
|
raise Exception()
|
|
else:
|
|
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]
|
|
def get_privkey_S(self):
|
|
return [int(i) for j in self._S for i in j]
|
|
def get_privkey_p(self):
|
|
return self._p
|
|
def set_pubkey(self, G):
|
|
try:
|
|
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
|
|
else:
|
|
self._G = G
|
|
def set_privkey_S(self, S):
|
|
try:
|
|
S = [S[i - self._k : i] for i in range(self._k, self._k * self._k + self._k, self._k)]
|
|
S = self._GF(S)
|
|
S_inv = np.linalg.inv(S)
|
|
except:
|
|
raise
|
|
else:
|
|
self._S = S
|
|
self._S_inv = S_inv
|
|
def set_privkey_p(self, p):
|
|
if sorted(p) != [i for i in range(self._n)]:
|
|
raise Exception()
|
|
else:
|
|
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 restore_pubkey(self):
|
|
self._G = self._S @ self._rs.G @ self._P
|
|
def restore_privkey_S(self):
|
|
S = self._G @ self._P_inv
|
|
S = self._GF(S[:, : self._k])
|
|
try:
|
|
S_inv = np.linalg.inv(S)
|
|
except:
|
|
raise
|
|
self._S = S
|
|
self._S_inv = S_inv
|
|
def restore_privkey_p(self):
|
|
G = self._rs.G
|
|
G = G.T
|
|
G = [[int(i) for i in j] for j in G]
|
|
GP = self._S_inv @ self._G
|
|
GP = GP.T
|
|
GP = [[int(i) for i in j] for j in GP]
|
|
p = [0 for i in range(self._n)]
|
|
f = False
|
|
for i in range(self._n):
|
|
f = False
|
|
for j in range(self._n):
|
|
if G[i] == GP[j]:
|
|
p[i] = j
|
|
f = True
|
|
break
|
|
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))
|
|
for i in range(self._n):
|
|
self._P[i, p[i]] = 1
|
|
self._P_inv[p[i], i] = 1
|
|
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
|
|
#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))
|
|
while np.linalg.det(S) == 0:
|
|
S = self._GF.Random((self._k, self._k))
|
|
self._S = S
|
|
self._S_inv = np.linalg.inv(S)
|
|
def _generate_P(self):
|
|
r = [i for i in range(self._n)]
|
|
p = []
|
|
for i in range(self._n):
|
|
p.append(r.pop(random.randint(0, self._n - 1 - i)))
|
|
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 _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]
|
|
|
|
if __name__ == "__main__":
|
|
main()
|