# Ticket #11573: 17437.patch

File 17437.patch, 15.5 KB (added by scotts, 9 years ago)

Implementation of the ElGamal? algorithm

• ## doc/en/reference/cryptography.rst

# HG changeset patch
# User scotts <sam.scott89@gmail.com>
# Date 1357994795 0
# Node ID 58499325d16fdd06e2e8fda88387046211f92ac8
# Parent  9641d73faa235c36af68660d25acf0407e12809e
trac 11573: Implemented ElGamal algorithm

diff --git a/doc/en/reference/cryptography.rst b/doc/en/reference/cryptography.rst
 a sage/crypto/block_cipher/miniaes sage/crypto/public_key/blum_goldwasser sage/crypto/public_key/elgamal sage/crypto/stream sage/crypto/stream_cipher sage/crypto/mq/mpolynomialsystem sage/crypto/mq/sbox sage/crypto/lattice No newline at end of file sage/crypto/lattice
• ## new file sage/crypto/public_key/elgamal.py

diff --git a/sage/crypto/public_key/elgamal.py b/sage/crypto/public_key/elgamal.py
new file mode 100644
 - """ ElGamal Encryption The ElGamal encryption system is an asymmetric public-key encryption scheme based on the discrete logarithm problem first proposed by Taher ElGamal in 1985 [ElGamal1985]_. This implementation is based on the description of the algorithm, as explained in section 8.4 of [MenezesEtAl1996]_. REFERENCES: .. [ElGamal1985] Taher ElGamal. A Public-Key Cryptosystem and a Signature Scheme Based on Discrete Logarithm. In *IEEE Transactions on Information Theory*. Vol 31 Issue 4, pp. 469--472, 1985. .. [MenezesEtAl1996] A. J. Menezes, P. C. van Oorschot, and S. A. Vanstone.Primes *Handbook of Applied Cryptography*. CRC Press, 1996. AUTHORS: - Sam Scott (2012-13): original implementation of algorithm as described in [MenezesEtAl1996]_ NOTE: The implementation of this class takes significant inspiration from the implementation of :class:sage.crypto.public_key.blum_goldwasser. """ #***************************************************************************** #       Copyright (C) 2012 Sam Scott # #  Distributed under the terms of the GNU General Public License (GPL) #  as published by the Free Software Foundation; either version 2 of #  the License, or (at your option) any later version. #                  http://www.gnu.org/licenses/ #***************************************************************************** from sage.crypto.cryptosystem import PublicKeyCryptosystem from sage.crypto.cipher import PublicKeyCipher from sage.crypto.util import * from sage.rings.arith import random_prime, is_prime, factor from sage.rings.finite_rings.integer_mod_ring import * from sage.misc.prandom import randint from sage.functions.other import Function_floor, Function_ceil from sage.functions.log import log from sage.rings.integer import * from sage.structure.factorization_integer import IntegerFactorization from sage.groups.abelian_gps.abelian_group import AbelianGroup, is_AbelianGroup from sage.monoids.string_monoid_element import is_BinaryStringMonoidElement floor = Function_floor() ceil = Function_ceil() class ElGamalCryptosystem(PublicKeyCryptosystem): r""" Creates a new ElGamal cryptosystem over a Abelian cyclic group G. G can also be the ring of integers mod a prime (This is due to the absense of an implementation of the unit group for the ring of integers). This class defines the methods to create a cipher using either the public or private keys. Also included are tools to create an ElGamal cryptosytsem over a random ring of integers mod p, where both p and the generator can be generated given security parameters. Note: There are also encode/decode methods that will convert ASCII messages to be used in the encryption/decryption algorithms. ALGORITHM: The ElGamalCryptosystem is defined over a cyclic group G, with a chosen generator g. The public key is then given by the generator raised to a chosen power, \alpha = g^a where the exponent a is the private key. Encryption is then performed by calculating a random exponent r, then the ciphertext is the pair (\gamma, \delta) with \gamma = g^r, \delta = m*alpha^r (for some message block m) Decryption recovers the message m by calculating \gamma^{-a} * \delta = g^{-ar} * m * g^{ar} = g^{ar-ar}*m = m INPUT: - G - the underlying group to be used for the cryptosystem. Must be either an Abelian group or a ring of integers - g - a specific generator of G to use OUTPUT: - An ElGamalCryptosystem EXAMPLES: Use static method to create a new ElGamal cryptosystem over integers mod p:: sage: from sage.crypto.public_key.elgamal import ElGamalCryptosystem sage: EG = ElGamalCryptosystem.cryptosystem_mod_p() sage: EG.order() 27496465690429282823791155294147456357502 sage: EG.generator() 12057815657733480208817982827558221154681 sage: EG ElGamal public-key encryption scheme over group: Ring of integers modulo 27496465690429282823791155294147456357503 with order: 27496465690429282823791155294147456357502 and generator: 12057815657733480208817982827558221154681 sage: a = randint(1, EG.order()-1) sage: alpha = pow(EG.generator(), a); alpha 7060296633046378010458276079271735634391 sage: eg_pub = EG(alpha, True) sage: eg_pri = EG(a, False) sage: message = "This is a message to be encrypted with ElGamal" sage: m_enc = EG.encode(message); m_enc [7180626514783912266103227848614357785049, 6134755570157836842402522509775728956994, 634902734711126073527859340710921633792] sage: ciphertext = eg_pub.encrypt(m_enc); ciphertext [(11082794267191942959070725657740089763683, 6672914587514854323748716077823573087384), (8035907320033825183679640612728522197237, 2329779004349004008807733002482225814400), (21090995572100184736846050189662037499275, 21838971013669608715349850172402611256459)] sage: decryption = eg_pri.decrypt(ciphertext); decryption [7180626514783912266103227848614357785049, 6134755570157836842402522509775728956994, 634902734711126073527859340710921633792] sage: EG.decode(decryption) 'This is a message to be encrypted with ElGamal' Create an ElGamal cryptosystem where the underlying group is an Abelian group:: sage: from sage.crypto.public_key.elgamal import ElGamalCryptosystem sage: G = AbelianGroup([131]) sage: EG = ElGamalCryptosystem(G); EG ElGamal public-key encryption scheme over group: Multiplicative Abelian Group isomorphic to C131 with order: 131 and generator: f^85 sage: EG.order() 131 sage: a = randint(1, EG.order()-1) sage: alpha = pow(EG.generator(), a); alpha f^6 sage: eg_pub = EG(alpha, True) sage: eg_pri = EG(a, False) """ def __init__(self, G, g=None): """ See the ElGamalCryptosystem method for detailed documentation. """ if is_IntegerModRing(G) and is_prime(G.order()): if g is None: g = G.multiplicative_generator()**randint(1, G.order()-2) else: if not g in G: raise ValueError("Given generator: %s is not in the provided group" % g) self._order = G.order()-1 else: if not (is_AbelianGroup(G) and G.is_cyclic()): raise TypeError("Group G must be a cyclic group") if g is None: g = G.gen()**randint(1, G.order()-1) else: if not g in G: raise ValueError("Given generator: %s is not in the provided group" % g) self._order = G.order() self._gen = g PublicKeyCryptosystem.__init__(self, G, G, G) def __repr__(self): """ A string representation of this ElGamal public-key encryption scheme. OUTPUT: - A string representation of this ElGamal public-key encryption scheme. EXAMPLES:: sage: from sage.crypto.public_key.elgamal import ElGamalCryptosystem sage: ElGamalCryptosystem.cryptosystem_mod_p(13, 3) ElGamal public-key encryption scheme over group: Ring of integers modulo 13 with order: 12 and generator: 3 """ return "ElGamal public-key encryption scheme over group: %s with order: %s and generator: %s" % (self.key_space(), self.order(), self.generator()) def __call__(self, key, public): """ Used to create the public/private key ciphers INPUT: - key - The public/private key to use with the cipher - public - The boolean argument used to set whether this is the public or the private key cipher being created (True for public, False for private) """ if public: if not key.parent() is self.key_space(): raise TypeError("Chosen public key: %s must be a member of the keyspace %s" % (key, self._key_space)) else: if not (key >=1 and key < self.order()): raise ValueError("Chosen private key: %s must be in the range: [ %s, %s]" % (key, 1, self.order()-1)) return ElGamalCipher(self, key, public) def order(self): """ Returns the order of the underlying group """ return self._order def generator(self): """ Returns the generator used in the cryptosystem """ return self._gen @staticmethod def cryptosystem_mod_p(p=None, alpha=None, sec=128): r""" A static utility function to create a new ElGamal cryptosystem over the ring of integers mod p. The argument sec will define the security parameter of the prime, as defined in [MenezesEtAl1996]_. A prime has security sec if the greatest factor of p-1 is greater than sec. INPUT: - p - A prime to use. By default will generate a random prime of length approximately sec bits - alpha - A generator for the for the ring. By default will generate a random generator - sec - The security paramter. """ if p is None: #generate random prime of roughly sec bits security t = 0 while t < sec: p = random_prime(2**(sec+8), 2**sec) #p = p - 2^(floor(log(p, 2))) f = factor(Integer(p-1)) #l = list(f) n = len(f) t = f[n-1][0] t = log(t, 2) t = floor(t) G = Integers(p) else: if not is_prime(p): raise TypeError("Chosen p must be prime") G = Integers(p) if alpha is None: #generate random generator of unit group alpha = G.multiplicative_generator()**randint(2, p-2) else: if not alpha in G: raise TypeError("Element: %s must be a member of G: %G" % (alpha, G)) return ElGamalCryptosystem(G, alpha) def encode(self, m): """ Convert a message m from a string (ASCII or binary) to the corresponding element in the plaintext space of the cryptosystem. Padding is performed on the last block of the message. Which is given by a '1' and trailing '0's. EXAMPLES:: sage: from sage.crypto.public_key.elgamal import * sage: EG = ElGamalCryptosystem.cryptosystem_mod_p() sage: m = EG.encode("This is an encoding of a message in the keyspace"); m [7180626514783912266104627553299360110874, 7792526574347972287376950590398713451014, 12835699585419200542137733230579982663680] sage: EG.decode(m) 'This is an encoding of a message in the keyspace' """ if not (isinstance(m, str) or is_BinaryStringMonoidElement(m)): raise TypeError("Message: %s must be either a valid ASCII string or a binary string" % m) #if m is BinaryString: #    m = bin_to_ascii(m) if isinstance(m, str): m = ascii_to_bin(m) m = str(m) b_len = floor(log(self.order(), 2)) m_len = len(m) num_padding = int(b_len - mod(m_len, b_len)) if num_padding > 0: m += '1' m = m.ljust(m_len + num_padding, '0') #m = m + '1' + pow('0', num_padding-1) #pad to fill bytes else: m += '1' m = m.ljust(m_len + b_len, '0') #m = m + '1' + '0'**b_len m_len += num_padding enc = [] PS = self.plaintext_space() if is_AbelianGroup(PS): for i in xrange(0, m_len/b_len): block = m[i*b_len:(i+1)*b_len] block = int(str(block), base=2) enc.append(PS([block])) else: for i in xrange(0, m_len/b_len): block = m[i*b_len:(i+1)*b_len] block = int(str(block), base=2) enc.append(PS(block)) return enc def decode(self, m): """ Return the string corresponding to the elements of the message space of the cryptosystem. See the encode method for more information. """ b_len = floor(log(self.order(), 2)) dec = "" CS = self.ciphertext_space() if is_AbelianGroup(CS): for i in xrange(0, len(m)): block = Integer(m[i].list()[0]).binary() #block = ("0"**(b_len-len(block))) + block block = block.zfill(b_len) dec = dec + block else: for i in xrange(0, len(m)): block = Integer(m[i]).binary() #block = ("0"**(b_len-len(block))) + block block = block.zfill(b_len) dec = dec + block dec = dec.rstrip('0') dec = dec[:-1] BS = BinaryStrings() #remove padding dec = BS(dec) dec = bin_to_ascii(dec) return dec class ElGamalCipher(PublicKeyCipher): r""" ElGamalCipher class. Instances of which are created from ElGamalCryptosystem. This is where the encryption/decryption takes place, using the chosen key. """ def __init__(self, parent, key, public): r""" Create an ElGamal cipher """ PublicKeyCipher.__init__(self, parent, key, public) def encrypt(self, M): """ The ElGamal encryption method, using the public key. INPUT: - M - The message to be encrypted. M must be a list of elements of the plaintext space of the cryptosystem """ if not self._public: raise TypeError("Encryption is not defined for the private key") n = self.parent().order() alpha = self.parent().generator() C = [] for i in xrange(0, len(M)): k = randint(1, n-1) gamma = alpha**k delta = M[i] * self._key**k C.append((gamma, delta)) return C def decrypt(self, C): """ The ElGamal decryption method, using the private key. INPUT: - C - The ciphertext to be decrypted. C must be a list of pairs of elements of the ciphertext space of the cryptosystem """ if self._public: raise TypeError("Decryption is not defined for the public key") M = [] for i in xrange(0, len(C)): m = ((C[i][0]**(self._key))**-1) * C[i][1] M.append(m) return M