ElGamal.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. #
  2. # ElGamal.py : ElGamal encryption/decryption and signatures
  3. #
  4. # Part of the Python Cryptography Toolkit
  5. #
  6. # Originally written by: A.M. Kuchling
  7. #
  8. # ===================================================================
  9. # The contents of this file are dedicated to the public domain. To
  10. # the extent that dedication to the public domain is not available,
  11. # everyone is granted a worldwide, perpetual, royalty-free,
  12. # non-exclusive license to exercise all rights associated with the
  13. # contents of this file for any purpose whatsoever.
  14. # No rights are reserved.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  20. # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  21. # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  22. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. # SOFTWARE.
  24. # ===================================================================
  25. __all__ = ['generate', 'construct', 'ElGamalKey']
  26. from Cryptodome import Random
  27. from Cryptodome.Math.Primality import ( generate_probable_safe_prime,
  28. test_probable_prime, COMPOSITE )
  29. from Cryptodome.Math.Numbers import Integer
  30. # Generate an ElGamal key with N bits
  31. def generate(bits, randfunc):
  32. """Randomly generate a fresh, new ElGamal key.
  33. The key will be safe for use for both encryption and signature
  34. (although it should be used for **only one** purpose).
  35. Args:
  36. bits (int):
  37. Key length, or size (in bits) of the modulus *p*.
  38. The recommended value is 2048.
  39. randfunc (callable):
  40. Random number generation function; it should accept
  41. a single integer *N* and return a string of random
  42. *N* random bytes.
  43. Return:
  44. an :class:`ElGamalKey` object
  45. """
  46. obj=ElGamalKey()
  47. # Generate a safe prime p
  48. # See Algorithm 4.86 in Handbook of Applied Cryptography
  49. obj.p = generate_probable_safe_prime(exact_bits=bits, randfunc=randfunc)
  50. q = (obj.p - 1) >> 1
  51. # Generate generator g
  52. while 1:
  53. # Choose a square residue; it will generate a cyclic group of order q.
  54. obj.g = pow(Integer.random_range(min_inclusive=2,
  55. max_exclusive=obj.p,
  56. randfunc=randfunc), 2, obj.p)
  57. # We must avoid g=2 because of Bleichenbacher's attack described
  58. # in "Generating ElGamal signatures without knowning the secret key",
  59. # 1996
  60. if obj.g in (1, 2):
  61. continue
  62. # Discard g if it divides p-1 because of the attack described
  63. # in Note 11.67 (iii) in HAC
  64. if (obj.p - 1) % obj.g == 0:
  65. continue
  66. # g^{-1} must not divide p-1 because of Khadir's attack
  67. # described in "Conditions of the generator for forging ElGamal
  68. # signature", 2011
  69. ginv = obj.g.inverse(obj.p)
  70. if (obj.p - 1) % ginv == 0:
  71. continue
  72. # Found
  73. break
  74. # Generate private key x
  75. obj.x = Integer.random_range(min_inclusive=2,
  76. max_exclusive=obj.p-1,
  77. randfunc=randfunc)
  78. # Generate public key y
  79. obj.y = pow(obj.g, obj.x, obj.p)
  80. return obj
  81. def construct(tup):
  82. r"""Construct an ElGamal key from a tuple of valid ElGamal components.
  83. The modulus *p* must be a prime.
  84. The following conditions must apply:
  85. .. math::
  86. \begin{align}
  87. &1 < g < p-1 \\
  88. &g^{p-1} = 1 \text{ mod } 1 \\
  89. &1 < x < p-1 \\
  90. &g^x = y \text{ mod } p
  91. \end{align}
  92. Args:
  93. tup (tuple):
  94. A tuple with either 3 or 4 integers,
  95. in the following order:
  96. 1. Modulus (*p*).
  97. 2. Generator (*g*).
  98. 3. Public key (*y*).
  99. 4. Private key (*x*). Optional.
  100. Raises:
  101. ValueError: when the key being imported fails the most basic ElGamal validity checks.
  102. Returns:
  103. an :class:`ElGamalKey` object
  104. """
  105. obj=ElGamalKey()
  106. if len(tup) not in [3,4]:
  107. raise ValueError('argument for construct() wrong length')
  108. for i in range(len(tup)):
  109. field = obj._keydata[i]
  110. setattr(obj, field, Integer(tup[i]))
  111. fmt_error = test_probable_prime(obj.p) == COMPOSITE
  112. fmt_error |= obj.g<=1 or obj.g>=obj.p
  113. fmt_error |= pow(obj.g, obj.p-1, obj.p)!=1
  114. fmt_error |= obj.y<1 or obj.y>=obj.p
  115. if len(tup)==4:
  116. fmt_error |= obj.x<=1 or obj.x>=obj.p
  117. fmt_error |= pow(obj.g, obj.x, obj.p)!=obj.y
  118. if fmt_error:
  119. raise ValueError("Invalid ElGamal key components")
  120. return obj
  121. class ElGamalKey(object):
  122. r"""Class defining an ElGamal key.
  123. Do not instantiate directly.
  124. Use :func:`generate` or :func:`construct` instead.
  125. :ivar p: Modulus
  126. :vartype d: integer
  127. :ivar g: Generator
  128. :vartype e: integer
  129. :ivar y: Public key component
  130. :vartype y: integer
  131. :ivar x: Private key component
  132. :vartype x: integer
  133. """
  134. #: Dictionary of ElGamal parameters.
  135. #:
  136. #: A public key will only have the following entries:
  137. #:
  138. #: - **y**, the public key.
  139. #: - **g**, the generator.
  140. #: - **p**, the modulus.
  141. #:
  142. #: A private key will also have:
  143. #:
  144. #: - **x**, the private key.
  145. _keydata=['p', 'g', 'y', 'x']
  146. def __init__(self, randfunc=None):
  147. if randfunc is None:
  148. randfunc = Random.new().read
  149. self._randfunc = randfunc
  150. def _encrypt(self, M, K):
  151. a=pow(self.g, K, self.p)
  152. b=( pow(self.y, K, self.p)*M ) % self.p
  153. return [int(a), int(b)]
  154. def _decrypt(self, M):
  155. if (not hasattr(self, 'x')):
  156. raise TypeError('Private key not available in this object')
  157. r = Integer.random_range(min_inclusive=2,
  158. max_exclusive=self.p-1,
  159. randfunc=self._randfunc)
  160. a_blind = (pow(self.g, r, self.p) * M[0]) % self.p
  161. ax=pow(a_blind, self.x, self.p)
  162. plaintext_blind = (ax.inverse(self.p) * M[1] ) % self.p
  163. plaintext = (plaintext_blind * pow(self.y, r, self.p)) % self.p
  164. return int(plaintext)
  165. def _sign(self, M, K):
  166. if (not hasattr(self, 'x')):
  167. raise TypeError('Private key not available in this object')
  168. p1=self.p-1
  169. K = Integer(K)
  170. if (K.gcd(p1)!=1):
  171. raise ValueError('Bad K value: GCD(K,p-1)!=1')
  172. a=pow(self.g, K, self.p)
  173. t=(Integer(M)-self.x*a) % p1
  174. while t<0: t=t+p1
  175. b=(t*K.inverse(p1)) % p1
  176. return [int(a), int(b)]
  177. def _verify(self, M, sig):
  178. sig = [Integer(x) for x in sig]
  179. if sig[0]<1 or sig[0]>self.p-1:
  180. return 0
  181. v1=pow(self.y, sig[0], self.p)
  182. v1=(v1*pow(sig[0], sig[1], self.p)) % self.p
  183. v2=pow(self.g, M, self.p)
  184. if v1==v2:
  185. return 1
  186. return 0
  187. def has_private(self):
  188. """Whether this is an ElGamal private key"""
  189. if hasattr(self, 'x'):
  190. return 1
  191. else:
  192. return 0
  193. def can_encrypt(self):
  194. return True
  195. def can_sign(self):
  196. return True
  197. def publickey(self):
  198. """A matching ElGamal public key.
  199. Returns:
  200. a new :class:`ElGamalKey` object
  201. """
  202. return construct((self.p, self.g, self.y))
  203. def __eq__(self, other):
  204. if bool(self.has_private()) != bool(other.has_private()):
  205. return False
  206. result = True
  207. for comp in self._keydata:
  208. result = result and (getattr(self.key, comp, None) ==
  209. getattr(other.key, comp, None))
  210. return result
  211. def __ne__(self, other):
  212. return not self.__eq__(other)
  213. def __getstate__(self):
  214. # ElGamal key is not pickable
  215. from pickle import PicklingError
  216. raise PicklingError
  217. # Methods defined in PyCryptodome that we don't support anymore
  218. def sign(self, M, K):
  219. raise NotImplementedError
  220. def verify(self, M, signature):
  221. raise NotImplementedError
  222. def encrypt(self, plaintext, K):
  223. raise NotImplementedError
  224. def decrypt(self, ciphertext):
  225. raise NotImplementedError
  226. def blind(self, M, B):
  227. raise NotImplementedError
  228. def unblind(self, M, B):
  229. raise NotImplementedError
  230. def size(self):
  231. raise NotImplementedError