pohlig_hellman_encryption.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Copyright 2019 The FATE Authors. All Rights Reserved.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. #
  18. import random
  19. from federatedml.secureprotol.gmpy_math import is_prime, invert, gcd, powmod, next_prime
  20. from federatedml.secureprotol.symmetric_encryption.symmetric_encryption import SymmetricKey, SymmetricCiphertext
  21. from federatedml.util import conversion
  22. class PohligHellmanCipherKey(SymmetricKey):
  23. """
  24. A commutative encryption scheme inspired by Pohlig, Stephen, and Martin Hellman. "An improved algorithm
  25. for computing logarithms over GF (p) and its cryptographic significance." 1978
  26. Enc(x) = x^a mod p, with public knowledge p being a prime and satisfying that (p - 1) / 2 is also a prime
  27. Dec(y) = y^(a^(-1) mod phi(p)) mod p
  28. """
  29. def __init__(self, mod_base, exponent=None):
  30. """
  31. :param exponent: int
  32. :param mod_base: int
  33. """
  34. super(PohligHellmanCipherKey, self).__init__()
  35. self.mod_base = mod_base # p
  36. if exponent is not None and gcd(exponent, mod_base - 1) != 1:
  37. raise ValueError("In Pohlig, exponent and the totient of the modulo base must be coprimes")
  38. self.exponent = exponent # a
  39. self.exponent_inverse = None if exponent is None else invert(exponent, mod_base - 1)
  40. @staticmethod
  41. def generate_key(key_size=1024):
  42. """
  43. Generate a self-typed object with public mod_base and vacant exponent
  44. :param key_size: int
  45. :return: PohligHellmanCipherKey
  46. """
  47. key_size_half = key_size // 2
  48. while True:
  49. mod_base_half = PohligHellmanCipherKey.generate_prime(2 ** (key_size_half - 1), 2 ** key_size_half - 1)
  50. mod_base = mod_base_half * 2 + 1
  51. if is_prime(mod_base):
  52. return PohligHellmanCipherKey(mod_base)
  53. @staticmethod
  54. def generate_prime(left, right):
  55. """
  56. Generate a prime over (left, right]
  57. :param left:
  58. :param right:
  59. :return:
  60. """
  61. while True:
  62. random_integer = random.randint(left, right)
  63. random_prime = next_prime(random_integer)
  64. if random_prime <= right:
  65. return random_prime
  66. def init(self):
  67. """
  68. Init self.exponent
  69. :return:
  70. """
  71. while True:
  72. self.exponent = random.randint(2, self.mod_base)
  73. if gcd(self.exponent, self.mod_base - 1) == 1:
  74. self.exponent_inverse = invert(self.exponent, self.mod_base - 1)
  75. break
  76. def encrypt(self, plaintext):
  77. if isinstance(plaintext, list):
  78. return self.encrypt_list(plaintext)
  79. return self.encrypt_single_val(plaintext)
  80. def encrypt_single_val(self, plaintext):
  81. """
  82. :param plaintext: int >= 0 / str / PohligHellmanCiphertext
  83. :return: PohligHellmanCiphertext
  84. """
  85. if isinstance(plaintext, str):
  86. plaintext = conversion.str_to_int(plaintext)
  87. elif isinstance(plaintext, PohligHellmanCiphertext):
  88. plaintext = plaintext.message
  89. elif not isinstance(plaintext, int):
  90. plaintext = conversion.str_to_int(str(plaintext))
  91. ciphertext = powmod(plaintext, self.exponent, self.mod_base)
  92. return PohligHellmanCiphertext(ciphertext)
  93. def encrypt_list(self, list_plaintext):
  94. ciphertext = [self.encrypt_single_val(p) for p in list_plaintext]
  95. return ciphertext
  96. def decrypt(self, ciphertext, decode_output=False):
  97. if isinstance(ciphertext, list):
  98. return self.decrypt_list(ciphertext, decode_output)
  99. return self.decrypt_single_val(ciphertext, decode_output)
  100. def decrypt_single_val(self, ciphertext, decode_output=False):
  101. """
  102. If decode, then call int_to_str() method to decode the output plaintext
  103. :param ciphertext: PohligHellmanCiphertext
  104. :param decode_output: bool
  105. :return: PohligHellmanCiphertext / str
  106. """
  107. if isinstance(ciphertext, PohligHellmanCiphertext):
  108. ciphertext = ciphertext.message
  109. elif isinstance(ciphertext, str):
  110. ciphertext = conversion.str_to_int(ciphertext)
  111. if decode_output:
  112. return conversion.int_to_str(powmod(ciphertext, self.exponent_inverse, self.mod_base))
  113. else:
  114. return PohligHellmanCiphertext(powmod(ciphertext, self.exponent_inverse, self.mod_base))
  115. def decrypt_list(self, ciphertext, decode_output):
  116. decrypt_result = [self.decrypt_single_val(c, decode_output) for c in ciphertext]
  117. return decrypt_result
  118. class PohligHellmanCiphertext(SymmetricCiphertext):
  119. """
  120. """
  121. def __init__(self, message):
  122. super(PohligHellmanCiphertext, self).__init__()
  123. self.message = message
  124. def __hash__(self):
  125. return self.message.__hash__()
  126. def __eq__(self, other):
  127. if not isinstance(other, PohligHellmanCiphertext):
  128. raise TypeError("Can only compare two PohligHellmanCiphertext objects")
  129. if self.message == other.message:
  130. return True
  131. else:
  132. return False