def generate_key_parameters(key_size: int) -> KeyParameters: """ Generates new random key parameters of size key_size. :param key_size: the key size in bits :return: the key parameters containing p, q and g. """ # Create safe primes while True: q = number.getPrime(key_size - 1) p = 2 * q + 1 if number.isPrime(p): break # Create generator of subgroup of order q. # # Since subgroups can just have the orders 1, 2, q, 2q=p-1 testing is easy. # https://crypto.stackexchange.com/questions/7983/elgamal-generation-of-g-value # https://crypto.stackexchange.com/questions/1451/elgamal-multiplicative-cyclic-group-and-key-generation while True: g = number.getRandomRange(3, p - 2) if (pow(g, q, p) == 1 and pow(g, 2, p) != 1): break return KeyParameters(p, q, g)
def _encrypt_key_element(key_element: int, public_key: PublicKey) -> (int, int): key_params = public_key.key_parameters if key_element >= key_params.p: raise ThresholdCryptoError('key element is larger than key parameter p') k = number.getRandomRange(1, key_params.q - 1) g_k = pow(key_params.g, k, key_params.p) # aka v g_ak = pow(public_key.g_a, k, key_params.p) c = (key_element * g_ak) % key_params.p return g_k, c
def test_key_encryption_decryption_without_enough_shares(self): r = number.getRandomRange(2, self.kp.q) testkey_element = pow(self.kp.g, r, self.kp.p) g_k, c = ThresholdCrypto._encrypt_key_element(testkey_element, self.pk) em = EncryptedMessage(g_k, c, '') reconstruct_shares = [self.shares[i] for i in [0, 4]] # choose 2 of 5 key shares partial_decryptions = [ ThresholdCrypto.compute_partial_decryption(em, share) for share in reconstruct_shares ] rec_testkey_element = ThresholdCrypto._combine_shares( partial_decryptions, em, self.tp, self.kp) self.assertNotEqual(testkey_element, rec_testkey_element)
def create_public_key_and_shares_centralized(key_params: KeyParameters, threshold_params: ThresholdParameters) -> (PublicKey, [KeyShare]): """ Creates a public key and n shares by choosing a random secret key and using it for computations. :param key_params: key parameters to use :param threshold_params: parameters t and n for the threshold scheme :return: (the public key, n key shares) """ a = number.getRandomRange(2, key_params.q - 2) g_a = pow(key_params.g, a, key_params.p) public_key = PublicKey(g_a, key_params) # Perform Shamir's secret sharing in Z_q polynom = number.PolynomMod.create_random_polynom(a, threshold_params.t - 1, key_params.q) supporting_points = range(1, threshold_params.n + 1) shares = [KeyShare(x, polynom.evaluate(x), key_params) for x in supporting_points] return public_key, shares
def encrypt_message(public_key: PublicKey, message: str) -> EncryptedMessage: """ Encrypt a message using a public key. A hybrid encryption approach is used to include advantages of symmetric encryption (fast, independent of message-length, integrity-preserving by using AE-scheme). Internally a combination of Salsa20 and Poly1305 from the cryptographic library NaCl is used. :param message: the message to be encrypted :param public_key: the public key :return: an encrypted message """ encoded_message = bytes(message, 'utf-8') key_params = public_key.parameters # Create random subgroup element and use its hash as symmetric key to prevent # attacks described in "Why Textbook ElGamal and RSA Encryption Are Insecure" # by Boneh et. al. r = number.getRandomRange(2, key_params.q) key_subgroup_element = pow(key_params.g, r, key_params.p) key_subgroup_element_byte_length = (key_subgroup_element.bit_length() + 7) // 8 element_bytes = key_subgroup_element.to_bytes( key_subgroup_element_byte_length, byteorder='big') try: symmetric_key = nacl.hash.blake2b( element_bytes, digest_size=nacl.secret.SecretBox.KEY_SIZE, encoder=nacl.encoding.RawEncoder) # Use derived symmetric key to encrypt the message box = nacl.secret.SecretBox(symmetric_key) encrypted = box.encrypt(encoded_message).hex() except nacl.exceptions.CryptoError as e: print('Encryption failed: ' + str(e)) raise ThresholdCryptoError('Message encryption failed.') # Use threshold scheme to encrypt the subgroup element used as hash input to derive the symmetric key g_k, c = ThresholdCrypto._encrypt_key_element(key_subgroup_element, public_key) return EncryptedMessage(g_k, c, encrypted)