Ticket #11573: 17437.patch

File 17437.patch, 15.5 KB (added by scotts, 7 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 b  
    1515   sage/crypto/block_cipher/miniaes
    1616
    1717   sage/crypto/public_key/blum_goldwasser
     18   sage/crypto/public_key/elgamal
    1819
    1920   sage/crypto/stream
    2021   sage/crypto/stream_cipher
     
    2728   sage/crypto/mq/mpolynomialsystem
    2829   sage/crypto/mq/sbox
    2930
    30    sage/crypto/lattice
    31  No newline at end of file
     31   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
    - +  
     1"""
     2ElGamal Encryption
     3
     4The ElGamal encryption system is an asymmetric public-key encryption scheme
     5based on the discrete logarithm problem first proposed by Taher ElGamal in 1985 [ElGamal1985]_.
     6
     7This implementation is based on the description of the algorithm, as explained
     8in section 8.4 of [MenezesEtAl1996]_.
     9
     10REFERENCES:
     11
     12.. [ElGamal1985] Taher ElGamal. A Public-Key Cryptosystem and a Signature
     13  Scheme Based on Discrete Logarithm. In *IEEE Transactions on Information Theory*.
     14  Vol 31 Issue 4, pp. 469--472, 1985.
     15
     16.. [MenezesEtAl1996] A. J. Menezes, P. C. van Oorschot, and S. A. Vanstone.Primes
     17  *Handbook of Applied Cryptography*. CRC Press, 1996.
     18
     19AUTHORS:
     20- Sam Scott (2012-13): original implementation of algorithm as described in [MenezesEtAl1996]_
     21
     22NOTE:
     23
     24The implementation of this class takes significant inspiration from the implementation
     25of :class:`sage.crypto.public_key.blum_goldwasser`.
     26
     27"""
     28
     29#*****************************************************************************
     30#       Copyright (C) 2012 Sam Scott <sam.scott89@gmail.com>
     31#
     32#  Distributed under the terms of the GNU General Public License (GPL)
     33#  as published by the Free Software Foundation; either version 2 of
     34#  the License, or (at your option) any later version.
     35#                  http://www.gnu.org/licenses/
     36#*****************************************************************************
     37
     38from sage.crypto.cryptosystem import PublicKeyCryptosystem
     39from sage.crypto.cipher import PublicKeyCipher
     40from sage.crypto.util import *
     41from sage.rings.arith import random_prime, is_prime, factor
     42from sage.rings.finite_rings.integer_mod_ring import *
     43from sage.misc.prandom import randint
     44from sage.functions.other import Function_floor, Function_ceil
     45from sage.functions.log import log
     46from sage.rings.integer import *
     47from sage.structure.factorization_integer import IntegerFactorization
     48from sage.groups.abelian_gps.abelian_group import AbelianGroup, is_AbelianGroup
     49from sage.monoids.string_monoid_element import is_BinaryStringMonoidElement
     50
     51
     52floor = Function_floor()
     53ceil = Function_ceil()
     54
     55class ElGamalCryptosystem(PublicKeyCryptosystem):
     56    r"""
     57    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).
     58
     59    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.
     60
     61    Note: There are also encode/decode methods that will convert ASCII messages to be used in the encryption/decryption algorithms.
     62
     63    ALGORITHM:
     64
     65    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.
     66
     67    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`)
     68
     69    Decryption recovers the message `m` by calculating `\gamma^{-a} * \delta = g^{-ar} * m * g^{ar} = g^{ar-ar}*m = m`
     70
     71    INPUT:
     72
     73    - ``G`` - the underlying group to be used for the cryptosystem. Must be either an Abelian group or a ring of integers
     74    - ``g`` - a specific generator of G to use
     75
     76    OUTPUT:
     77
     78    - An ElGamalCryptosystem
     79
     80    EXAMPLES:
     81
     82    Use static method to create a new ElGamal cryptosystem over integers mod p::
     83
     84        sage: from sage.crypto.public_key.elgamal import ElGamalCryptosystem
     85        sage: EG = ElGamalCryptosystem.cryptosystem_mod_p()
     86        sage: EG.order()
     87        27496465690429282823791155294147456357502
     88        sage: EG.generator()
     89        12057815657733480208817982827558221154681
     90        sage: EG
     91        ElGamal public-key encryption scheme over group: Ring of integers modulo 27496465690429282823791155294147456357503 with order: 27496465690429282823791155294147456357502 and generator: 12057815657733480208817982827558221154681
     92        sage: a = randint(1, EG.order()-1)
     93        sage: alpha = pow(EG.generator(), a); alpha
     94        7060296633046378010458276079271735634391
     95        sage: eg_pub = EG(alpha, True)
     96        sage: eg_pri = EG(a, False)
     97        sage: message = "This is a message to be encrypted with ElGamal"
     98        sage: m_enc = EG.encode(message); m_enc
     99        [7180626514783912266103227848614357785049, 6134755570157836842402522509775728956994, 634902734711126073527859340710921633792]
     100        sage: ciphertext = eg_pub.encrypt(m_enc); ciphertext
     101        [(11082794267191942959070725657740089763683, 6672914587514854323748716077823573087384), (8035907320033825183679640612728522197237, 2329779004349004008807733002482225814400), (21090995572100184736846050189662037499275, 21838971013669608715349850172402611256459)]
     102        sage: decryption = eg_pri.decrypt(ciphertext); decryption
     103        [7180626514783912266103227848614357785049, 6134755570157836842402522509775728956994, 634902734711126073527859340710921633792]
     104        sage: EG.decode(decryption)
     105        'This is a message to be encrypted with ElGamal'
     106
     107    Create an ElGamal cryptosystem where the underlying group is an Abelian group::
     108
     109        sage: from sage.crypto.public_key.elgamal import ElGamalCryptosystem
     110        sage: G = AbelianGroup([131])
     111        sage: EG = ElGamalCryptosystem(G); EG
     112        ElGamal public-key encryption scheme over group: Multiplicative Abelian Group isomorphic to C131 with order: 131 and generator: f^85
     113        sage: EG.order()
     114        131
     115        sage: a = randint(1, EG.order()-1)
     116        sage: alpha = pow(EG.generator(), a); alpha
     117        f^6
     118        sage: eg_pub = EG(alpha, True)
     119        sage: eg_pri = EG(a, False)
     120
     121    """
     122
     123    def __init__(self, G, g=None):
     124        """
     125        See the ``ElGamalCryptosystem`` method for detailed documentation.
     126        """
     127        if is_IntegerModRing(G) and is_prime(G.order()):
     128            if g is None:
     129                g = G.multiplicative_generator()**randint(1, G.order()-2)
     130            else:
     131                if not g in G:
     132                    raise ValueError("Given generator: %s is not in the provided group" % g)
     133            self._order = G.order()-1
     134        else:
     135            if not (is_AbelianGroup(G) and G.is_cyclic()):
     136                raise TypeError("Group G must be a cyclic group")
     137
     138            if g is None:
     139                g = G.gen()**randint(1, G.order()-1)
     140            else:
     141                if not g in G:
     142                    raise ValueError("Given generator: %s is not in the provided group" % g)
     143            self._order = G.order()
     144
     145        self._gen = g
     146        PublicKeyCryptosystem.__init__(self, G, G, G)
     147
     148    def __repr__(self):
     149        """
     150        A string representation of this ElGamal public-key encryption
     151        scheme.
     152
     153        OUTPUT:
     154
     155        - A string representation of this ElGamal public-key
     156          encryption scheme.
     157
     158        EXAMPLES::
     159
     160            sage: from sage.crypto.public_key.elgamal import ElGamalCryptosystem
     161            sage: ElGamalCryptosystem.cryptosystem_mod_p(13, 3)
     162            ElGamal public-key encryption scheme over group: Ring of integers modulo 13 with order: 12 and generator: 3
     163        """
     164        return "ElGamal public-key encryption scheme over group: %s with order: %s and generator: %s" % (self.key_space(), self.order(), self.generator())
     165
     166    def __call__(self, key, public):
     167        """
     168        Used to create the public/private key ciphers
     169
     170        INPUT:
     171
     172        - ``key`` - The public/private key to use with the cipher
     173
     174        - ``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)
     175        """
     176        if public:
     177            if not key.parent() is self.key_space():
     178                raise TypeError("Chosen public key: %s must be a member of the keyspace %s" % (key, self._key_space))
     179        else:
     180            if not (key >=1 and key < self.order()):
     181                raise ValueError("Chosen private key: %s must be in the range: [ %s, %s]" % (key, 1, self.order()-1))
     182        return ElGamalCipher(self, key, public)
     183
     184    def order(self):
     185        """
     186        Returns the order of the underlying group
     187        """
     188        return self._order
     189
     190    def generator(self):
     191        """
     192        Returns the generator used in the cryptosystem
     193        """
     194        return self._gen
     195
     196    @staticmethod
     197    def cryptosystem_mod_p(p=None, alpha=None, sec=128):
     198        r"""
     199            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.
     200
     201        INPUT:
     202
     203        - ``p`` - A prime to use. By default will generate a random prime of length approximately sec bits
     204
     205        - ``alpha`` - A generator for the for the ring. By default will generate a random generator
     206
     207        - ``sec`` - The security paramter.
     208        """
     209        if p is None:
     210            #generate random prime of roughly sec bits security
     211            t = 0
     212            while t < sec:
     213                p = random_prime(2**(sec+8), 2**sec)
     214                #p = p - 2^(floor(log(p, 2)))
     215                f = factor(Integer(p-1))
     216                #l = list(f)
     217                n = len(f)
     218                t = f[n-1][0]
     219                t = log(t, 2)
     220                t = floor(t)
     221            G = Integers(p)
     222        else:
     223            if not is_prime(p):
     224                raise TypeError("Chosen p must be prime")
     225            G = Integers(p)
     226
     227        if alpha is None:
     228            #generate random generator of unit group
     229            alpha = G.multiplicative_generator()**randint(2, p-2)
     230        else:
     231            if not alpha in G:
     232                raise TypeError("Element: %s must be a member of G: %G" % (alpha, G))
     233
     234        return ElGamalCryptosystem(G, alpha)
     235
     236    def encode(self, m):
     237        """
     238        Convert a message m from a string (ASCII or binary) to the corresponding element in the plaintext space of the cryptosystem.
     239
     240        Padding is performed on the last block of the message. Which is given by a '1' and trailing '0's.
     241            EXAMPLES::
     242
     243                sage: from sage.crypto.public_key.elgamal import *
     244                sage: EG = ElGamalCryptosystem.cryptosystem_mod_p()
     245                sage: m = EG.encode("This is an encoding of a message in the keyspace"); m
     246                [7180626514783912266104627553299360110874, 7792526574347972287376950590398713451014, 12835699585419200542137733230579982663680]
     247                sage: EG.decode(m)
     248                'This is an encoding of a message in the keyspace'
     249        """
     250        if not (isinstance(m, str) or is_BinaryStringMonoidElement(m)):
     251            raise TypeError("Message: %s must be either a valid ASCII string or a binary string" % m)
     252
     253        #if m is BinaryString:
     254        #    m = bin_to_ascii(m)
     255        if isinstance(m, str):
     256            m = ascii_to_bin(m)
     257        m = str(m)
     258
     259        b_len = floor(log(self.order(), 2))
     260        m_len = len(m)
     261        num_padding = int(b_len - mod(m_len, b_len))
     262        if num_padding > 0:
     263            m += '1'
     264            m = m.ljust(m_len + num_padding, '0')
     265            #m = m + '1' + pow('0', num_padding-1) #pad to fill bytes
     266        else:
     267            m += '1'
     268            m = m.ljust(m_len + b_len, '0')
     269            #m = m + '1' + '0'**b_len
     270
     271        m_len += num_padding
     272
     273        enc = []
     274        PS = self.plaintext_space()
     275        if is_AbelianGroup(PS):
     276            for i in xrange(0, m_len/b_len):
     277                block = m[i*b_len:(i+1)*b_len]
     278                block = int(str(block), base=2)
     279                enc.append(PS([block]))
     280        else:
     281            for i in xrange(0, m_len/b_len):
     282                block = m[i*b_len:(i+1)*b_len]
     283                block = int(str(block), base=2)
     284                enc.append(PS(block))
     285
     286        return enc
     287
     288    def decode(self, m):
     289        """
     290        Return the string corresponding to the elements of the message space of the cryptosystem.
     291
     292        See the ``encode`` method for more information.
     293        """
     294        b_len = floor(log(self.order(), 2))
     295        dec = ""
     296
     297        CS = self.ciphertext_space()
     298        if is_AbelianGroup(CS):
     299            for i in xrange(0, len(m)):
     300                block = Integer(m[i].list()[0]).binary()
     301                #block = ("0"**(b_len-len(block))) + block
     302                block = block.zfill(b_len)
     303                dec = dec + block
     304        else:
     305            for i in xrange(0, len(m)):
     306                block = Integer(m[i]).binary()
     307                #block = ("0"**(b_len-len(block))) + block
     308                block = block.zfill(b_len)
     309                dec = dec + block
     310        dec = dec.rstrip('0')
     311        dec = dec[:-1]
     312        BS = BinaryStrings()
     313        #remove padding
     314        dec = BS(dec)
     315        dec = bin_to_ascii(dec)
     316        return dec
     317
     318class ElGamalCipher(PublicKeyCipher):
     319    r"""
     320    ElGamalCipher class. Instances of which are created from ElGamalCryptosystem.
     321    This is where the encryption/decryption takes place, using the chosen key.
     322    """
     323
     324    def __init__(self, parent, key, public):
     325        r"""
     326        Create an ElGamal cipher
     327        """
     328        PublicKeyCipher.__init__(self, parent, key, public)
     329
     330    def encrypt(self, M):
     331        """
     332        The ElGamal encryption method, using the public key.
     333
     334        INPUT:
     335        - ``M`` - The message to be encrypted. M must be a list of elements of the plaintext space of the cryptosystem
     336        """
     337        if not self._public:
     338            raise TypeError("Encryption is not defined for the private key")
     339
     340        n = self.parent().order()
     341        alpha = self.parent().generator()
     342        C = []
     343        for i in xrange(0, len(M)):
     344            k = randint(1, n-1)
     345            gamma = alpha**k
     346            delta = M[i] * self._key**k
     347            C.append((gamma, delta))
     348        return C
     349
     350    def decrypt(self, C):
     351        """
     352        The ElGamal decryption method, using the private key.
     353
     354        INPUT:
     355        - ``C`` - The ciphertext to be decrypted. C must be a list of pairs of elements of the ciphertext space of the cryptosystem
     356        """
     357        if self._public:
     358            raise TypeError("Decryption is not defined for the public key")
     359
     360        M = []
     361        for i in xrange(0, len(C)):
     362            m = ((C[i][0]**(self._key))**-1) * C[i][1]
     363            M.append(m)
     364        return M