Commits (1)
 ... ... @@ -15,6 +15,7 @@ Cryptography sage/crypto/block_cipher/miniaes sage/crypto/public_key/blum_goldwasser sage/crypto/public_key/elgamal_cryptosystem sage/crypto/stream sage/crypto/stream_cipher ... ...
 ... ... @@ -14,3 +14,6 @@ from lfsr import ( lfsr_sequence, lfsr_autocorrelation, lfsr_connection_polynomial) from public_key.elgamal_cryptosystem import ( ElGamalCryptosystem) \ No newline at end of file
 from blum_goldwasser import BlumGoldwasser from elgamal_cryptosystem import ElGamalCryptosystem
 r""" ElGamal public-key encryption scheme ElGamal is an asymmetric, non-deterministic public-key encryption system. Its security is equivalent to Diffie-Hellman, since both rely on the hardness of the discrete logarithm problem. Also see the Wikipedia article _ on this scheme. REFERENCES: - Alfred Menezes, et al. *Handbook of Applied Cryptography; Chapter 8, Public-Key Encryption*. http://cacr.uwaterloo.ca/hac/about/chap8.pdf. Copyright 1997. - Thomas Pornin. *When to use RSA and when ElGamal asymmetric encryption*. http://crypto.stackexchange.com/questions/1677/when-to-use-rsa-and-when-elgamal-asymmetric-encryption. Accessed 11 March 2015. AUTHORS: - Peter Story (2015-03) - initial release under the GPL """ ########################################################################### # Copyright (c) 2015 # Peter Story # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # http://www.gnu.org/licenses/ ########################################################################### from sage.misc.randstate import set_random_seed from sage.rings.arith import euler_phi from sage.rings.arith import gcd from sage.rings.arith import is_prime from sage.rings.arith import power_mod from sage.rings.arith import primitive_root from sage.rings.integer import Integer from sage.rings.finite_rings.integer_mod import Mod as mod from sage.rings.integer_ring import ZZ from sage.sets.primes import Primes class ElGamalCryptosystem: r""" The ElGamal public-key encryption scheme. EXAMPLES: The following is an encryption/decryption example.:: sage: ec = ElGamalCryptosystem(); ec The ElGamal public-key encryption scheme. sage: p = 101 sage: public, private = ec.calculate_keys(p) sage: P = 42 sage: C = ec.encrypt(P, public) sage: M = ec.decrypt(C, private); M 42 sage: M == P True """ def __eq__(self, other): """ Compare this ElGamalCryptosystem object with other. INPUT: - other - an object. OUTPUT: - True if both self and other are ElGamalCryptosystem objects. False otherwise. Since ElGamalCryptosystem objects do not have state, any two ElGamalCryptosystem objects are equivalent. EXAMPLES:: sage: ec1 = ElGamalCryptosystem() sage: ec2 = ElGamalCryptosystem() sage: ec1 == ec2 True """ if (self.__class__ == other.__class__): return True else: return False def __repr__(self): """ A string representation of this ElGamal public-key encryption scheme. OUTPUT: - A string representation of the ElGamal public-key encryption scheme. EXAMPLES:: sage: ElGamalCryptosystem() The ElGamal public-key encryption scheme. """ return "The ElGamal public-key encryption scheme." def decrypt(self, C, K): r""" Apply the ElGamal public-key encryption scheme to decrypt the ciphertext C using the private key K. INPUT: - C - an integer ciphertext and header, of the form (gamma, delta) resulting from encrypting a plaintext using the ElGamal public-key encryption algorithm. - K - a private key, stored as a tuple (p, a). OUTPUT: - The plaintext resulting from decrypting the ciphertext C using the ElGamal public-key decryption algorithm. ALGORITHM: The ElGamal public-key decryption algorithm is described as follows: #. Let C be the ciphertext and header, of the form (\gamma, \delta). Remember that \gamma = g^k \text { mod } (p) and \delta = (g^a)^k * M \text { mod } (p), where M is the plaintext message. #. Let (p, a) be the private key. Remember that a is the power used to compute g^a in the corresponding public key (p, g, g^a). #. The plaintext is recovered by M = \gamma^{\phi(p) - a} * \delta \text{ mod } (p). This works because it is equivalent to \left(g^k\right)^{\phi(p) - a} \left( g^{k} \right)^a M = \left(g^{\phi(p)}\right)^k M which by Euler's Theorem is equivalent to 1^k M \equiv M EXAMPLES: An example of decryption.:: sage: ec = ElGamalCryptosystem() sage: p = 101 sage: public, private = ec.calculate_keys(p) sage: P = 42 sage: C = ec.encrypt(P, public) sage: M = ec.decrypt(C, private); M; P == M 42 True TESTS: Setup for tests.:: sage: ec = ElGamalCryptosystem() sage: p = 101 sage: public, private = ec.calculate_keys(p) Decryption should recover the original message.:: sage: P = 42 sage: C = ec.encrypt(P, public) sage: M = ec.decrypt(C, private); P == M True sage: P = ZZ.random_element(2, p) sage: C = ec.encrypt(P, public) sage: ec.decrypt(C, private) == P True A header (gamma) outside the range [2, p-1] should be rejected.:: sage: P = 42 sage: C = ec.encrypt(P, public) sage: C = (-1, C[1]) sage: M = ec.decrypt(C, private) Traceback (most recent call last): ... ValueError: The header must be an integer greater than 0 and less than p. Ciphertext (delta) outside the range [2, p-1] should be rejected.:: sage: P = 42 sage: C = ec.encrypt(P, public) sage: C = (C[0], -1) sage: M = ec.decrypt(C, private) Traceback (most recent call last): ... ValueError: The ciphertext must be an integer greater than 0 and less than p. """ # private key p, a = K # Header and Ciphertext, respectively gamma, delta = C # Computer the Euler characteristic of p euler_phi = p - 1 # sanity check if not ( (gamma > 0) and (gamma < p) and (gamma in ZZ) ): raise ValueError("The header must be an integer greater than 0 and less than p.") if not ( (delta > 0) and (delta < p) and (delta in ZZ) ): raise ValueError("The ciphertext must be an integer greater than 0 and less than p.") # perform the decryption return (power_mod(gamma, euler_phi - a, p) * delta) % p def encrypt(self, P, K): r""" Apply the ElGamal public-key encryption scheme to encrypt the plaintext P using the public key K. INPUT: - P - the plaintext, a number in the range [2, p-1]. - K - a public key, stored as a tuple (p, g, g^a) where n is the modulus and e is the exponent. OUTPUT: - The ciphertext and header of the form (gamma, delta) resulting from encrypting P using the public key K. ALGORITHM: The ElGamal public-key encryption algorithm is described as follows: #. Let (p, g, g^a) be a public key, where g is a generator for the group \mathds{Z}_p. #. Let M be the plaintext message, such that 0 < M < p. #. First, choose an integer k, such that 0 < k < p . #. Next, compute \gamma = g^k \text{ mod } (p), which is known as the header. #. Finally, compute the ciphertext \delta = (g^a)^k * m \text{ mod } (p). #. The ciphertext is packaged with the header as C = (\gamma, \delta). EXAMPLES: An example of encryption. P = 42 is our plaintext, and C = (\gamma, \delta) is the resulting ciphertext.:: sage: ec = ElGamalCryptosystem() sage: p = 101 sage: public, private = ec.calculate_keys(p) sage: P = 42 sage: C = ec.encrypt(P, public) Note that encryption is not deterministic, because k is chosen randomly each time a message is encrypted.:: sage: ec = ElGamalCryptosystem() sage: p = 5915587277 sage: public, private = ec.calculate_keys(p) sage: P = 42 sage: ec.encrypt(P, public) != ec.encrypt(P, public) True TESTS: Setup for tests.:: sage: ec = ElGamalCryptosystem() sage: p = 101 sage: public, private = ec.calculate_keys(p) Plaintext outside the range [2, p-1] should be rejected.:: sage: P = 0 sage: C = ec.encrypt(P, public) Traceback (most recent call last): ... ValueError: The plaintext must be an integer greater than 1 and less than p. Non-integer plaintext should be rejected.:: sage: P = "cat" sage: C = ec.encrypt(P, public) Traceback (most recent call last): ... ValueError: The plaintext must be an integer greater than 1 and less than p. """ # public key p, g, g_to_a = K # sanity check if (P not in ZZ) or (P < 2) or (P >= p): raise ValueError("The plaintext must be an integer greater than 1 and less than p.") # Choose a temporary number set_random_seed() k = ZZ.random_element(2, p) # Calculate gamma gamma = power_mod(g, k, p) # Calculate delta delta = (power_mod(g_to_a, k, p) * P) % p # Return the header and the encrypted message return (gamma, delta) def calculate_keys(self, p, enforce_primes=True): r""" Return an ElGamal public and private key pair corresponding to the prime p. INPUT: - p - a prime number. - enforce_primes - whether to require that p be prime. OUTPUT: - The ElGamal public and private keys as a tuple ((p, g, g^a), (p, a)). EXAMPLES: Compute an ElGamal public and private key pair corresponding to the supplied prime.:: sage: ec = ElGamalCryptosystem() sage: p = 19 sage: public, private = ec.calculate_keys(p) For teaching purposes, you can supply a number which is not a prime. This is also helpful if you are using very large primes, to avoid the expensive computation of checking primeness. Frequently, generating the keys will fail because it will be impossible to find a generator for the group.:: sage: ec = ElGamalCryptosystem() sage: n = 8 sage: public, private = ec.calculate_keys(n, False) Traceback (most recent call last): ... ValueError: no primitive root However, some composite numbers do have generators. In this case, key generation will succeed. Unfortunately, without using a prime, decryption will (usually) fail.:: sage: ec = ElGamalCryptosystem() sage: n = 250 sage: public, private = ec.calculate_keys(n, False) sage: P = 42 sage: P != ec.decrypt(ec.encrypt(P, public), private) True TESTS: Setup for tests.:: sage: ec = ElGamalCryptosystem() Key generation should succeed when prime p is supplied.:: sage: ec.calculate_keys(3) ((3, 2, 1), (3, 2)) Input p must be an integer.:: sage: public, private = ec.calculate_keys("my_bad_key") Traceback (most recent call last): ... ValueError: p must be an integer. Input p must be a prime.:: sage: public, private = ec.calculate_keys(21) Traceback (most recent call last): ... ValueError: p must be a prime. Allow p to be composite if enforce_primes is set to False.:: sage: public, private = ec.calculate_keys(250, False) Of course, composite p are not guaranteed to work, since composites are not guaranteed to have generators.:: sage: public, private = ec.calculate_keys(8, False) Traceback (most recent call last): ... ValueError: no primitive root """ if p not in ZZ: raise ValueError("p must be an integer.") if enforce_primes and (not is_prime(p)): raise ValueError("p must be a prime.") # We are guaranteed a primitive root because p is prime g = primitive_root(p) # Choose the private key a such that 1 < a < p set_random_seed() a = ZZ.random_element(2, p) # Calculate g^a mod (p) g_to_a = power_mod(g, a, p) public_key = (p, g, g_to_a) private_key = (p, a) return (public_key, private_key)