Commit dbc70e70 authored by Sophie Leuenberger's avatar Sophie Leuenberger
Browse files

Upload crypto_builder.sage

parent 1d50f9a3
import random
import time
def build_McEliece_cryptosystem(q=7, n=20, m=3, k=5):
"""Set up a McEliece cryptosystem with
:param q: prime base
:param n: length of linear code
:param m: extension degree
:param k: dimension of underlying GRS code
:return: public and private key of McEliece cryptosystem
"""
base_field = GF(q)
extension_field = GF(q**m, 'z')
# --- Check parameters: --- #
# Code length must be smaller or equal than number of field elements:
if n > extension_field.cardinality():
raise ValueError("Invalid parameters: n > |F|")
# Code length must be greater than subspace dimension:
if k > n:
raise ValueError("Invalid parameters: k > n")
print "Generating Code with n={0}, k={1}, m={2} and q={3}".format(n, k, m, q)
# --- Start time measurement: --- #
t0 = time.time()
# --- Construct random support and multiplier vectors: --- #
# Support: vector containing distinct elements of the extension field:
a = extension_field.gen()
support = [a^i for i in range(n-1)]+[0]
# Multiplier: vector containing non-zero elements of the extension field:
l = list(extension_field)
l.remove(extension_field.zero())
multiplier = []
for i in range(n):
multiplier.append(random.choice(l))
# --- Build alternant code: --- #
Alt_k = generate_alternant_code(extension_field, n, m, k, support, multiplier)
# --- Build error-correcting pair --- #
A, B = generate_ecp(extension_field, n, k, support, multiplier)
# --- Compute max. number of error positions: --- #
t = floor(k/2)
# --- Build public key: --- #
G_public, P, S = hide_structure(Alt_k)
# --- Stop time measurement: --- #
t1 = time.time() - t0
print "Dimension of alternant code={0}. Time elapsed: {1} seconds.".format(Alt_k.dimension(), t1)
# --- Return public and private key: --- #
return G_public, P, S, Alt_k, A, B, t
def generate_alternant_code(F, n, m, k, supp, mult):
"""Generate an alternant code with parameters
:param F: extension field
:param n: code length
:param m: extension degree
:param k: dimension of underlying GRS code
:param supp: support vector
:param mult: multiplier vector
:return: Alternant code of lenght n and degree k
"""
# --- Compute generalized Reed-Solomon code over F with dimension k: --- #
# Build generator matrix:
G_GRS_k = matrix(F, k, n, 0)
for i in range(k):
for j in range(n):
G_GRS_k[i,j] = mult[j]*supp[j]**i
# Build its dual code:
GRS_k = LinearCode(G_GRS_k)
GRS_k_dual = GRS_k.dual_code()
# --- Build an alternant code over the base field F_q
# (subfield subcode of the GRS-code): --- #
# Parity check matrix:
H_ = GRS_k_dual.parity_check_matrix()
H = matrix(F.base_ring(), m*H_.nrows(), n, 0)
# Write every entry of H_ over the base field F_q:
FF = F.vector_space()
for i in range(H_.nrows()):
for j in range(n):
elem = H_[i,j]
coerced_elem = FF(elem)
for l in range(m):
H[l+i*m, j] = coerced_elem[l]
# Construct a generator matrix G for the alternant code
# from the parity check matrix H (G*H^T=0):
ker = H.right_kernel()
G_alternant = ker.basis_matrix()
Alt_k = LinearCode(G_alternant)
# --- Check parameters: --- #
if Alt_k.dimension() == 0:
raise ValueError("Invalid parameters.")
# --- Return the alternant code --- #
return Alt_k
def generate_ecp(extension_field, n, k, supp, mult):
"""Generate an error-correcting pair (A,B) for the alternant code
:param extension_field: base_field of underlying GRS code
:param n: code length
:param k: degree of alternant code
:param supp: support vector of alternant code
:param mult: multiplier vector of alternant code
:return: error-correcting pair (A, B)
"""
# Compute the maximal errors which can be added to a codeword:
t = floor(k/2)
# --- Construct error-correcting pair: --- #
# Generator matrix for A:
G_A = matrix(extension_field, t+1, n, 0)
for i in range(G_A.nrows()):
for j in range(G_A.ncols()):
G_A[i, j] = supp[j]**i
# Build linear code A:
A = LinearCode(G_A)
# Generator matrix for B:
G_B = matrix(extension_field, t, n, 0)
for i in range(G_B.nrows()):
for j in range(G_B.ncols()):
G_B[i,j] = mult[j]*(supp[j]**i)
# Build linear code B:
B = LinearCode(G_B)
# --- Return the linear codes A and B: --- #
return A, B
def hide_structure(code):
"""Hide structure of a code by modifying its generator matrix G
:param code: code whose structure should be hidden
:return: modified generator matrix, permutation matrix P, scrambler matrix S
"""
# --- Get information of the code: --- #
# Get the base field of the code:
K = code.base_field()
# Get the code length:
n = code.generator_matrix().ncols()
# Get the code dimension:
dim_code = code.generator_matrix().nrows()
# --- Multiply the generator matrix by a random permutation
# and a random scrambler matrix: --- #
# Compute random permutation matrix P:
indices = range(n)
random.shuffle(indices)
P = matrix(K, n, n, 0)
for i in xrange(n):
P[i, indices[i]] = K.one()
# Compute random scrambler matrix S:
S = random_matrix(K, dim_code, dim_code, algorithm='unimodular')
# --- Return the modified generator matrix G'=S*G*P --- #
return S*code.generator_matrix()*P, P, S
def build_decrypter(path):
"""Build the decrypter script
:param path: name of the private folder
"""
out_str = '''import time
def run_decryption(P, S, Alt_k, A, B, message):
"""Run decryption on message with given private key arguments"""
# --- Start time measurement: --- #
t0 = time.time()
# --- Decryption process: --- #
# Get base field of the code:
base_field = Alt_k.base_field()
# transform the message to a vector over the base field:
word = vector(base_field, message)
# Start decryption process:
code = decrypt(P, S, Alt_k, A, B, word)
# if the decrypting process fails, return -1:
if code == -1:
return -1
# --- Stop time measurement: --- #
t1 = t1 = time.time() - t0
print "Time consumption for decryption process:{0} seconds.".format(t1)
# --- Return the decrypted word: --- #
return code
def decrypt(P, S, Alt_k, A, B, word):
"""Decrypt word with given private key arguments"""
# Multiply given word by P^{-1}:
y = word * P.inverse()
# Start decoding process:
code_word = decode_using_ECP(Alt_k, A, B, y)
# if the decoding process fails, return -1:
if code_word==-1:
return -1
# Compute the matrix S*G:
solve_mat = S*Alt_k.generator_matrix()
# Retrieve original message:
message = solve_mat.solve_left(code_word)
# --- Return the original message: --- #
return message
def decode_using_ECP(C, A, B, y):
"""Decode y = c + e where c is an element of C and e is an error vector"""
# --- Start time measurement: --- #
t0 = time.time()
# --- Initialization: --- #
F = A.base_field()
n = len(y)
ky_element = 0
K = F.base_ring()
# --- Compute the kernel of received word y: --- #
ky_element = compute_kernel_element(A, B, y)
# if kernel computation failed, return -1:
if ky_element == 0:
return -1
J = []
for i in range(n):
if ky_element[i] == 0:
J.append(i)
# --- Solve linear equation to get error vector: --- #
# Get parity check matrix:
H = C.parity_check_matrix()
# Compute complement of J:
J_comp = range(H.ncols())
for i in J:
J_comp.remove(i)
# Build matrix consisting of J-indexed columns of H:
H_J = H[:,J]
# Build matrix consisting of J_comp-indexed columns of H:
H_J_comp = H[:,J_comp]
# Build vector consisting of J-indexed entries of y:
y_J = vector(F,[y[i] for i in xrange(n) if i in J])
# Build vector consisting of J_comp-indexed entries of y:
y_J_comp = vector(F,[y[i] for i in xrange(n) if i not in J])
# Compute the solution space:
temp = H_J*y_J.column()+H_J_comp*(y_J_comp.column())
sol_ = H_J.solve_right(temp)
sol_e = vector(K, n)
# Compute the error vector:
index=0
for j in J:
sol_e[j] = sol_[index][0]
index+=1
# Compute codeword as y-error
sol_c = y-sol_e
# --- Stop time measurement: --- #
t1 = time.time() - t0
print "Time consumption for decoding process:{0} seconds.".format(t1)
# --- Return the decoded codeword: --- #
return sol_c
def compute_kernel_element(A, B, y):
"""Compute the kernel ker_y of y and return an arbitrary element of ker_y"""
# --- Compute the tranformation matrix of the kernel map: --- #
T = matrix(A.base_ring(), A.dimension(), B.dimension(), 0)
for i in range(A.dimension()):
for j in range(B.dimension()):
T[i,j] = inner_star_prod(A.basis()[i], B.basis()[j], y)
# --- Compute the kernel and check that it is non-empty: --- #
ker = kernel(T)
if ker.cardinality() == 0:
raise ValueError("Something went wrong! Empty kernel!")
# --- Compute a random element of K_y: --- #
lambdas = ker.random_element()
while lambdas == ker.zero():
lambdas = ker.random_element()
A_base_mat = matrix(A.base_ring(), A.basis())
a_rand = lambdas * A_base_mat
# --- Return an arbitrary element of K_y: --- #
return a_rand
def inner_star_prod(a,b,y):
"""Compute the dot product of a*b and y"""
# Get vector length:
n = len(y)
# Compute <a*b,y>:
res = 0
for i in range(n):
res += a[i]*b[i]*y[i]
# --- Return the reulting field element --- #
return res
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('message', help='encoded message you want to decode (put " around it)', type=str)
args = parser.parse_args()
# --- Load the private key from path: --- #
# Permutation matrix P:
p = load('P.sobj')
# Scrambler matrix S:
s = load('S.sobj')
# Alternant code alt_k:
alt_k = load('alt_k.sobj')
# Error-correcting pair (A,B):
A = load('A.sobj')
B = load('B.sobj')
# --- Get message which needs to be decrypted: --- #
message = eval(args.message)
# --- Start decryption process: --- #
word = run_decryption(p, s, alt_k, A, B, message[0])
if word == -1:
print "Decryption process failed."
# Print the decrypted message to the command line:
print word
'''
filehandle = open(path + '/decrypter.sage', 'w')
filehandle.write(out_str)
filehandle.close()
def build_encrypter(name):
out_str = '''import time
def generate_random_key(keylength, base_field):
"""Generate a random key to be encrypted"""
key = []
for i in range(keylength):
key.append(base_field.random_element())
# --- Return key: --- #
return key
if __name__ == '__main__':
import random
import time
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('gen_mat', help='File with generator matrix (part of public key)', type=str)
parser.add_argument('t', help='t (part of public key)', type=int)
args = parser.parse_args()
# --- Get information about the scrambled generator matrix: --- #
gen_mat = load(args.gen_mat)
n = gen_mat.ncols()
l = gen_mat.nrows()
base_field = gen_mat.base_ring()
# --- Generate a random key: --- #
key = generate_random_key(l, base_field)
print "Randomly generated key is: {0}".format(key)
# --- Start time measurement: --- #
t0 = time.time()
# --- Encrytion process: --- #
# Encode:
encoded_key = vector(base_field, key)*gen_mat
t = args.t
encrypted = []
# Add errors to encoded key:
check_list = random.sample(range(n), t)
error_list = [0 if x not in check_list else random.randint(1, base_field.characteristic()-1)
for x in xrange(n)]
error = vector(base_field, error_list)
encrypted.append(encoded_key + error)
# --- Stop time measurement: --- #
t1 = time.time() - t0
print "Time consumption for encryption process:{0} seconds.".format(t1)
# --- print encrypted key to command line: --- #
out_str = str(encrypted).replace(' ','')
print out_str
'''
file_handle = open(name + '_encrypter.sage', 'w')
file_handle.write(out_str)
file_handle.close()
if __name__ == '__main__':
import argparse
import os
# --- Parser for command line input: --- #
parser = argparse.ArgumentParser()
parser.add_argument('q', help='prime base for the algorithm, at least 31, best >= 163', type=int, default=271)
parser.add_argument('m', help='extension degree', type=int, default=3)
parser.add_argument('n', help='length of code words. Smaller or equal than q^m', type=int, default=20)
parser.add_argument('k', help='dimension of the code', type=int, default=5)
parser.add_argument('name', help='give your code a name. Will create a folder of same name in cwd with decoder',
type=str)
args = parser.parse_args()
# --- Build McEliece cryptosystem: --- #
public, P, S, alt_k, A, B, t = build_McEliece_cryptosystem(args.q, args.n, args.m, args.k)
# --- Store private and public key at given path: --- #
path_string = './{0}'.format(args.name)
if not os.path.exists(path_string):
os.makedirs(path_string)
else:
print "Path already exists. Probably overwriting existing code."
alt_k.save(path_string + '/alt_k')
public.save(path_string + '.public')
t.save(path_string + '.errorCorrectionCapability')
A.save(path_string + '/A')
B.save(path_string + '/B')
P.save(path_string + '/P')
S.save(path_string + '/S')
# --- Write encrypter and decrypter scripts: --- #
build_decrypter(path_string)
build_encrypter(path_string)
# --- Print information for user: --- #
print 'Generator Matrix is in "{0}.public.sobj". t is {1}'.format(path_string, t)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment