123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- """Paillier encryption library for partially homomorphic encryption."""
- #
- # Copyright 2019 The FATE Authors. All Rights Reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- #
- import random
- from federatedml.secureprotol import gmpy_math
- from federatedml.secureprotol.fixedpoint import FixedPointNumber
- class PaillierKeypair(object):
- def __init__(self):
- pass
- @staticmethod
- def generate_keypair(n_length=1024):
- """return a new :class:`PaillierPublicKey` and :class:`PaillierPrivateKey`.
- """
- p = q = n = None
- n_len = 0
- while n_len != n_length:
- p = gmpy_math.getprimeover(n_length // 2)
- q = p
- while q == p:
- q = gmpy_math.getprimeover(n_length // 2)
- n = p * q
- n_len = n.bit_length()
- public_key = PaillierPublicKey(n)
- private_key = PaillierPrivateKey(public_key, p, q)
- return public_key, private_key
- class PaillierPublicKey(object):
- """Contains a public key and associated encryption methods.
- """
- def __init__(self, n):
- self.g = n + 1
- self.n = n
- self.nsquare = n * n
- self.max_int = n // 3 - 1
- def __repr__(self):
- hashcode = hex(hash(self))[2:]
- return "<PaillierPublicKey {}>".format(hashcode[:10])
- def __eq__(self, other):
- return self.n == other.n
- def __hash__(self):
- return hash(self.n)
- def apply_obfuscator(self, ciphertext, random_value=None):
- """
- """
- r = random_value or random.SystemRandom().randrange(1, self.n)
- obfuscator = gmpy_math.powmod(r, self.n, self.nsquare)
- return (ciphertext * obfuscator) % self.nsquare
- def raw_encrypt(self, plaintext, random_value=None):
- """
- """
- if not isinstance(plaintext, int):
- raise TypeError("plaintext should be int, but got: %s" %
- type(plaintext))
- if plaintext >= (self.n - self.max_int) and plaintext < self.n:
- # Very large plaintext, take a sneaky shortcut using inverses
- neg_plaintext = self.n - plaintext # = abs(plaintext - nsquare)
- neg_ciphertext = (self.n * neg_plaintext + 1) % self.nsquare
- ciphertext = gmpy_math.invert(neg_ciphertext, self.nsquare)
- else:
- ciphertext = (self.n * plaintext + 1) % self.nsquare
- ciphertext = self.apply_obfuscator(ciphertext, random_value)
- return ciphertext
- def encrypt(self, value, precision=None, random_value=None):
- """Encode and Paillier encrypt a real number value.
- """
- if isinstance(value, FixedPointNumber):
- value = value.decode()
- encoding = FixedPointNumber.encode(value, self.n, self.max_int, precision)
- obfuscator = random_value or 1
- ciphertext = self.raw_encrypt(encoding.encoding, random_value=obfuscator)
- encryptednumber = PaillierEncryptedNumber(self, ciphertext, encoding.exponent)
- if random_value is None:
- encryptednumber.apply_obfuscator()
- return encryptednumber
- class PaillierPrivateKey(object):
- """Contains a private key and associated decryption method.
- """
- def __init__(self, public_key, p, q):
- if not p * q == public_key.n:
- raise ValueError("given public key does not match the given p and q")
- if p == q:
- raise ValueError("p and q have to be different")
- self.public_key = public_key
- if q < p:
- self.p = q
- self.q = p
- else:
- self.p = p
- self.q = q
- self.psquare = self.p * self.p
- self.qsquare = self.q * self.q
- self.q_inverse = gmpy_math.invert(self.q, self.p)
- self.hp = self.h_func(self.p, self.psquare)
- self.hq = self.h_func(self.q, self.qsquare)
- def __eq__(self, other):
- return self.p == other.p and self.q == other.q
- def __hash__(self):
- return hash((self.p, self.q))
- def __repr__(self):
- hashcode = hex(hash(self))[2:]
- return "<PaillierPrivateKey {}>".format(hashcode[:10])
- def h_func(self, x, xsquare):
- """Computes the h-function as defined in Paillier's paper page.
- """
- return gmpy_math.invert(self.l_func(gmpy_math.powmod(self.public_key.g,
- x - 1, xsquare), x), x)
- def l_func(self, x, p):
- """computes the L function as defined in Paillier's paper.
- """
- return (x - 1) // p
- def crt(self, mp, mq):
- """the Chinese Remainder Theorem as needed for decryption.
- return the solution modulo n=pq.
- """
- u = (mp - mq) * self.q_inverse % self.p
- x = (mq + (u * self.q)) % self.public_key.n
- return x
- def raw_decrypt(self, ciphertext):
- """return raw plaintext.
- """
- if not isinstance(ciphertext, int):
- raise TypeError("ciphertext should be an int, not: %s" %
- type(ciphertext))
- mp = self.l_func(gmpy_math.powmod(ciphertext,
- self.p - 1, self.psquare),
- self.p) * self.hp % self.p
- mq = self.l_func(gmpy_math.powmod(ciphertext,
- self.q - 1, self.qsquare),
- self.q) * self.hq % self.q
- return self.crt(mp, mq)
- def decrypt(self, encrypted_number):
- """return the decrypted & decoded plaintext of encrypted_number.
- """
- if not isinstance(encrypted_number, PaillierEncryptedNumber):
- raise TypeError("encrypted_number should be an PaillierEncryptedNumber, \
- not: %s" % type(encrypted_number))
- if self.public_key != encrypted_number.public_key:
- raise ValueError("encrypted_number was encrypted against a different key!")
- encoded = self.raw_decrypt(encrypted_number.ciphertext(be_secure=False))
- encoded = FixedPointNumber(encoded,
- encrypted_number.exponent,
- self.public_key.n,
- self.public_key.max_int)
- decrypt_value = encoded.decode()
- return decrypt_value
- class PaillierEncryptedNumber(object):
- """Represents the Paillier encryption of a float or int.
- """
- def __init__(self, public_key, ciphertext, exponent=0):
- self.public_key = public_key
- self.__ciphertext = ciphertext
- self.exponent = exponent
- self.__is_obfuscator = False
- if not isinstance(self.__ciphertext, int):
- raise TypeError("ciphertext should be an int, not: %s" % type(self.__ciphertext))
- if not isinstance(self.public_key, PaillierPublicKey):
- raise TypeError("public_key should be a PaillierPublicKey, not: %s" % type(self.public_key))
- def ciphertext(self, be_secure=True):
- """return the ciphertext of the PaillierEncryptedNumber.
- """
- if be_secure and not self.__is_obfuscator:
- self.apply_obfuscator()
- return self.__ciphertext
- def apply_obfuscator(self):
- """ciphertext by multiplying by r ** n with random r
- """
- self.__ciphertext = self.public_key.apply_obfuscator(self.__ciphertext)
- self.__is_obfuscator = True
- def __add__(self, other):
- if isinstance(other, PaillierEncryptedNumber):
- return self.__add_encryptednumber(other)
- else:
- return self.__add_scalar(other)
- def __radd__(self, other):
- return self.__add__(other)
- def __sub__(self, other):
- return self + (other * -1)
- def __rsub__(self, other):
- return other + (self * -1)
- def __rmul__(self, scalar):
- return self.__mul__(scalar)
- def __truediv__(self, scalar):
- return self.__mul__(1 / scalar)
- def __mul__(self, scalar):
- """return Multiply by an scalar(such as int, float)
- """
- if isinstance(scalar, FixedPointNumber):
- scalar = scalar.decode()
- encode = FixedPointNumber.encode(scalar, self.public_key.n, self.public_key.max_int)
- plaintext = encode.encoding
- if plaintext < 0 or plaintext >= self.public_key.n:
- raise ValueError("Scalar out of bounds: %i" % plaintext)
- if plaintext >= self.public_key.n - self.public_key.max_int:
- # Very large plaintext, play a sneaky trick using inverses
- neg_c = gmpy_math.invert(self.ciphertext(False), self.public_key.nsquare)
- neg_scalar = self.public_key.n - plaintext
- ciphertext = gmpy_math.powmod(neg_c, neg_scalar, self.public_key.nsquare)
- else:
- ciphertext = gmpy_math.powmod(self.ciphertext(False), plaintext, self.public_key.nsquare)
- exponent = self.exponent + encode.exponent
- return PaillierEncryptedNumber(self.public_key, ciphertext, exponent)
- def increase_exponent_to(self, new_exponent):
- """return PaillierEncryptedNumber:
- new PaillierEncryptedNumber with same value but having great exponent.
- """
- if new_exponent < self.exponent:
- raise ValueError("New exponent %i should be great than old exponent %i" % (new_exponent, self.exponent))
- factor = pow(FixedPointNumber.BASE, new_exponent - self.exponent)
- new_encryptednumber = self.__mul__(factor)
- new_encryptednumber.exponent = new_exponent
- return new_encryptednumber
- def __align_exponent(self, x, y):
- """return x,y with same exponet
- """
- if x.exponent < y.exponent:
- x = x.increase_exponent_to(y.exponent)
- elif x.exponent > y.exponent:
- y = y.increase_exponent_to(x.exponent)
- return x, y
- def __add_scalar(self, scalar):
- """return PaillierEncryptedNumber: z = E(x) + y
- """
- if isinstance(scalar, FixedPointNumber):
- scalar = scalar.decode()
- encoded = FixedPointNumber.encode(scalar,
- self.public_key.n,
- self.public_key.max_int,
- max_exponent=self.exponent)
- return self.__add_fixpointnumber(encoded)
- def __add_fixpointnumber(self, encoded):
- """return PaillierEncryptedNumber: z = E(x) + FixedPointNumber(y)
- """
- if self.public_key.n != encoded.n:
- raise ValueError("Attempted to add numbers encoded against different public keys!")
- # their exponents must match, and align.
- x, y = self.__align_exponent(self, encoded)
- encrypted_scalar = x.public_key.raw_encrypt(y.encoding, 1)
- encryptednumber = self.__raw_add(x.ciphertext(False), encrypted_scalar, x.exponent)
- return encryptednumber
- def __add_encryptednumber(self, other):
- """return PaillierEncryptedNumber: z = E(x) + E(y)
- """
- if self.public_key != other.public_key:
- raise ValueError("add two numbers have different public key!")
- # their exponents must match, and align.
- x, y = self.__align_exponent(self, other)
- encryptednumber = self.__raw_add(x.ciphertext(False), y.ciphertext(False), x.exponent)
- return encryptednumber
- def __raw_add(self, e_x, e_y, exponent):
- """return the integer E(x + y) given ints E(x) and E(y).
- """
- ciphertext = gmpy_math.mpz(e_x) * gmpy_math.mpz(e_y) % self.public_key.nsquare
- return PaillierEncryptedNumber(self.public_key, int(ciphertext), exponent)
|