# # 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 functools import hashlib from collections import Iterable import numpy as np from federatedml.util import LOGGER from Cryptodome import Random from Cryptodome.PublicKey import RSA from federatedml.feature.instance import Instance from federatedml.secureprotol import gmpy_math from federatedml.secureprotol.fate_paillier import PaillierKeypair from federatedml.secureprotol.fate_paillier import PaillierEncryptedNumber from federatedml.secureprotol.random import RandomPads try: from ipcl_python import PaillierKeypair as IpclPaillierKeypair except ImportError: pass _TORCH_VALID = False try: import torch _TORCH_VALID = True except ImportError: pass class Encrypt(object): def __init__(self): self.public_key = None self.privacy_key = None def generate_key(self, n_length=0): pass def set_public_key(self, public_key): pass def get_public_key(self): pass def set_privacy_key(self, privacy_key): pass def get_privacy_key(self): pass def encrypt(self, value): pass def decrypt(self, value): pass def raw_encrypt(self, value): pass def raw_decrypt(self, value): pass def encrypt_list(self, values): result = [self.encrypt(msg) for msg in values] return result def decrypt_list(self, values): result = [self.decrypt(msg) for msg in values] return result def distribute_decrypt(self, X): decrypt_table = X.mapValues(lambda x: self.recursive_decrypt(x)) return decrypt_table def distribute_encrypt(self, X): encrypt_table = X.mapValues(lambda x: self.recursive_encrypt(x)) return encrypt_table def distribute_raw_decrypt(self, X): return X.mapValues(lambda x: self.recursive_raw_decrypt(x)) def distribute_raw_encrypt(self, X): return X.mapValues(lambda x: self.recursive_raw_encrypt(x)) def _recursive_func(self, obj, func): if isinstance(obj, np.ndarray): if len(obj.shape) == 1: return np.reshape([func(val) for val in obj], obj.shape) else: return np.reshape( [self._recursive_func(o, func) for o in obj], obj.shape ) elif isinstance(obj, Iterable): return type(obj)( self._recursive_func(o, func) if isinstance(o, Iterable) else func(o) for o in obj ) else: return func(obj) def recursive_encrypt(self, X): return self._recursive_func(X, self.encrypt) def recursive_decrypt(self, X): return self._recursive_func(X, self.decrypt) def recursive_raw_encrypt(self, X): return self._recursive_func(X, self.raw_encrypt) def recursive_raw_decrypt(self, X): return self._recursive_func(X, self.raw_decrypt) class RsaEncrypt(Encrypt): def __init__(self): super(RsaEncrypt, self).__init__() self.e = None self.d = None self.n = None self.p = None self.q = None def generate_key(self, rsa_bit=1024): random_generator = Random.new().read rsa = RSA.generate(rsa_bit, random_generator) self.e = rsa.e self.d = rsa.d self.n = rsa.n self.p = rsa.p self.q = rsa.q def get_key_pair(self): return self.e, self.d, self.n, self.p, self.q def set_public_key(self, public_key): self.e = public_key["e"] self.n = public_key["n"] def get_public_key(self): return self.e, self.n def set_privacy_key(self, privacy_key): self.d = privacy_key["d"] self.n = privacy_key["n"] def get_privacy_key(self): return self.d, self.n def encrypt(self, value): if self.e is not None and self.n is not None and self.p is not None and self.q is not None: cp, cq = gmpy_math.crt_coefficient(self.p, self.q) return gmpy_math.powmod_crt(value, self.e, self.n, self.p, self.q, cp, cq) if self.e is not None and self.n is not None: return gmpy_math.powmod(value, self.e, self.n) else: return None def decrypt(self, value): if self.d is not None and self.n is not None: return gmpy_math.powmod(value, self.d, self.n) else: return None class PaillierEncrypt(Encrypt): def __init__(self): super(PaillierEncrypt, self).__init__() def generate_key(self, n_length=1024): self.public_key, self.privacy_key = PaillierKeypair.generate_keypair( n_length=n_length ) def get_key_pair(self): return self.public_key, self.privacy_key def set_public_key(self, public_key): self.public_key = public_key def get_public_key(self): return self.public_key def set_privacy_key(self, privacy_key): self.privacy_key = privacy_key def get_privacy_key(self): return self.privacy_key def encrypt(self, value): if self.public_key is not None: return self.public_key.encrypt(value) else: return None def decrypt(self, value): if self.privacy_key is not None: return self.privacy_key.decrypt(value) else: return None def raw_encrypt(self, plaintext, exponent=0): cipher_int = self.public_key.raw_encrypt(plaintext) paillier_num = PaillierEncryptedNumber(public_key=self.public_key, ciphertext=cipher_int, exponent=exponent) return paillier_num def raw_decrypt(self, ciphertext): return self.privacy_key.raw_decrypt(ciphertext.ciphertext()) def recursive_raw_encrypt(self, X, exponent=0): raw_en_func = functools.partial(self.raw_encrypt, exponent=exponent) return self._recursive_func(X, raw_en_func) class IpclPaillierEncrypt(Encrypt): """ A class to perform Paillier encryption with Intel Paillier Cryptosystem Library (IPCL) """ def __init__(self): super(IpclPaillierEncrypt, self).__init__() def generate_key(self, n_length=1024): self.public_key, self.privacy_key = IpclPaillierKeypair.generate_keypair( n_length=n_length ) def get_key_pair(self): return self.public_key, self.privacy_key def set_public_key(self, public_key): self.public_key = public_key def get_public_key(self): return self.public_key def set_privacy_key(self, privacy_key): self.privacy_key = privacy_key def get_privacy_key(self): return self.privacy_key def encrypt(self, value): if self.public_key is not None: return self.public_key.encrypt(value) else: return None def decrypt(self, value): if self.privacy_key is not None: return self.privacy_key.decrypt(value) else: return None def raw_encrypt(self, plaintext, exponent=0): """ Encrypt without applying obfuscator. Returns: (PaillierEncryptedNumber from `ipcl_python`): one ciphertext """ return self.public_key.raw_encrypt(plaintext) def raw_decrypt(self, ciphertext): """ Decrypt without constructing `FixedPointNumber`. Returns: (int or list): raw value(s) """ return self.privacy_key.raw_decrypt(ciphertext) def encrypt_list(self, values): """Encrypt a list of raw values into one ciphertext. Returns: (PaillierEncryptedNumber from `ipcl_python`): all in one single ciphertext """ return self.encrypt(values) def decrypt_list(self, values): """ Decrypt input values. If the type is list or 1-d numpy array, use `decrypt_list` of the parent class. Ohterwise, the type will be a 0-d numpy array, which contains one single ciphertext of multiple raw values. Use `item(0)` to fetch the ciphertext and then decrypt. Returns: (list): a list of raw values """ if np.ndim(values) >= 1: return super().decrypt_list(values) return self.decrypt(values.item(0)) def recursive_raw_encrypt(self, X, exponent=0): raw_en_func = functools.partial(self.raw_encrypt, exponent=exponent) return self._recursive_func(X, raw_en_func) class PadsCipher(Encrypt): def __init__(self): super().__init__() self._uuid = None self._rands = None self._amplify_factor = 1 def set_self_uuid(self, uuid): self._uuid = uuid def set_amplify_factor(self, factor): self._amplify_factor = factor def set_exchanged_keys(self, keys): self._seeds = { uid: v & 0xFFFFFFFF for uid, v in keys.items() if uid != self._uuid } self._rands = { uid: RandomPads(v & 0xFFFFFFFF) for uid, v in keys.items() if uid != self._uuid } def encrypt(self, value): if isinstance(value, np.ndarray): ret = value for uid, rand in self._rands.items(): if uid > self._uuid: ret = rand.add_rand_pads(ret, 1.0 * self._amplify_factor) else: ret = rand.add_rand_pads(ret, -1.0 * self._amplify_factor) return ret if _TORCH_VALID and isinstance(value, torch.Tensor): ret = value.numpy() for uid, rand in self._rands.items(): if uid > self._uuid: ret = rand.add_rand_pads(ret, 1.0 * self._amplify_factor) else: ret = rand.add_rand_pads(ret, -1.0 * self._amplify_factor) return torch.Tensor(ret) ret = value for uid, rand in self._rands.items(): if uid > self._uuid: ret += rand.rand(1)[0] * self._amplify_factor else: ret -= rand.rand(1)[0] * self._amplify_factor return ret def encrypt_table(self, table): def _pad(key, value, seeds, amplify_factor): has_key = int(hashlib.md5(f"{key}".encode("ascii")).hexdigest(), 16) # LOGGER.debug(f"hash_key: {has_key}") cur_seeds = {uid: has_key + seed for uid, seed in seeds.items()} # LOGGER.debug(f"cur_seeds: {cur_seeds}") rands = {uid: RandomPads(v & 0xFFFFFFFF) for uid, v in cur_seeds.items()} if isinstance(value, np.ndarray): ret = value for uid, rand in rands.items(): if uid > self._uuid: ret = rand.add_rand_pads(ret, 1.0 * amplify_factor) else: ret = rand.add_rand_pads(ret, -1.0 * amplify_factor) return key, ret elif isinstance(value, Instance): ret = value.features for uid, rand in rands.items(): if uid > self._uuid: ret = rand.add_rand_pads(ret, 1.0 * amplify_factor) else: ret = rand.add_rand_pads(ret, -1.0 * amplify_factor) value.features = ret return key, value else: ret = value for uid, rand in rands.items(): if uid > self._uuid: ret += rand.rand(1)[0] * self._amplify_factor else: ret -= rand.rand(1)[0] * self._amplify_factor return key, ret f = functools.partial( _pad, seeds=self._seeds, amplify_factor=self._amplify_factor ) return table.map(f) def decrypt(self, value): return value