def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of PrivateKey from the given file. Arguments: filename::string -- The name of a file containing the private key in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored private key file. """ # Create a new serializer object for the PrivateKey structure definition serializer = SerializerClass(PrivateKey_serialize_structure_definition) # Deserialize the PrivateKey instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError, e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid private key. The " \ "following error occurred while trying to deserialize the " \ "file contents: %s" % (filename, str(e)))
def str_to_num(num_str, base, value_name): try: return int(num_str, base) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid private key. The " \ "stored value for %s is not a valid integer in " \ "base %d representation." % (filename, value_name, base))
class PrivateKey: """ An ElGamal private key object used for decryption. Attributes: cryptosystem::EGCryptoSystem -- The ElGamal cryptosystem in which this key is defined. public_key::PublicKey -- The associated public key. """ def __eq__(self, other): """ Implements PrivateKey equality. """ if (isinstance(other, PrivateKey) and (other.cryptosystem == self.cryptosystem) and (other.public_key == self.public_key) and (other._key == self._key)): return True else: return False def __ne__(self, other): """ Implements PrivateKey inequality. """ return not self.__eq__(other) def __init__(self, cryptosystem, private_key_value): """ Creates a new private key. Should not be invoked directly. Instead of using this constructor from outside of PloneVoteCryptoLib, please use the class methods EGCryptoSystem.new_key_pair() or PrivateKey.from_file(file). Arguments: cryptosystem::EGCryptoSystem-- The ElGamal cryptosystem in which this key is defined. private_key_value::long -- The actual value of the private key. """ public_key_value = pow(cryptosystem.get_generator(), private_key_value, cryptosystem.get_prime()) self.cryptosystem = cryptosystem self.public_key = PublicKey(cryptosystem, public_key_value) self._key = private_key_value def decrypt_to_bitstream(self, ciphertext, task_monitor=None, force=False): """ Decrypts the given ciphertext into a bitstream. If the bitstream was originally encrypted with PublicKey.encrypt_X(), then this method returns a bitstream following the format described in Note 001 of the Ciphertext.py file: [size (64 bits) | message (size bits) | padding (X bits) ] Arguments: ciphertext::Ciphertext -- An encrypted Ciphertext object. task_monitor::TaskMonitor -- A task monitor for this task. force:bool -- Set this to true if you wish to force a decryption attempt, even when the ciphertext's stored public key fingerprint does not match that of the public key associated with this private key. Returns: bitstream::Bitstream -- A bitstream containing the unencrypted data. Throws: IncompatibleCiphertextError -- The given ciphertext does not appear to be decryptable with the selected private key. """ # Check that the public key fingerprint stored in the ciphertext # matches the public key associated with this private key. if (not force): if (ciphertext.nbits != self.cryptosystem.get_nbits()): raise IncompatibleCiphertextError("The given ciphertext is " \ "not decryptable with the selected private key: " \ "incompatible cryptosystem/key sizes.") if (ciphertext.pk_fingerprint != self.public_key.get_fingerprint()): raise IncompatibleCiphertextError("The given ciphertext is " \ "not decryptable with the selected private key: " \ "public key fingerprint mismatch.") # We read and decrypt the ciphertext block by block # See "Handbook of Applied Cryptography" Algorithm 8.18 bitstream = BitStream() block_size = self.cryptosystem.get_nbits() - 1 prime = self.cryptosystem.get_prime() key = self._key # Check if we have a task monitor and register with it if (task_monitor != None): # One tick per block ticks = ciphertext.get_length() decrypt_task_mon = \ task_monitor.new_subtask("Decrypt data", expected_ticks = ticks) for gamma, delta in ciphertext: assert max(gamma, delta) < 2**(block_size + 1), \ "The ciphertext object includes blocks larger than the " \ "expected block size." m = (pow(gamma, prime - 1 - key, prime) * delta) % prime bitstream.put_num(m, block_size) if (task_monitor != None): decrypt_task_mon.tick() return bitstream def decrypt_to_text(self, ciphertext, task_monitor=None, force=False): """ Decrypts the given ciphertext into its text contents as a string This method assumes that the ciphertext contains an encrypted stream of data in the format of Note 001 of the Ciphertext.py file, were message contains string information (as opposed to a binary format). [size (64 bits) | message (size bits) | padding (X bits) ] Arguments: ciphertext::Ciphertext -- An encrypted Ciphertext object, containing data in the above format. task_monitor::TaskMonitor -- A task monitor for this task. force:bool -- Set to true if you wish to force a decryption attempt, even when the ciphertext's stored public key fingerprint does not match that of the public key associated with this private key. Returns: string::string -- Decrypted message as a string. Throws: IncompatibleCiphertextError -- The given ciphertext does not appear to be decryptable with the selected private key. """ bitstream = self.decrypt_to_bitstream(ciphertext, task_monitor, force) bitstream.seek(0) length = bitstream.get_num(64) return bitstream.get_string(length) def to_file(self, filename, SerializerClass=serialize.XMLSerializer): """ Saves this private key to a file. Arguments: filename::string -- The path to the file in which to store the serialized PrivateKey object. SerializerClass::class -- The class that provides the serialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate serialize_to_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) """ # Create a new serializer object for the PrivateKey structure definition serializer = SerializerClass(PrivateKey_serialize_structure_definition) # Helper function to translate large numbers to their hexadecimal # string representation def num_to_hex_str(num): hex_str = hex(num)[2:] # Remove leading '0x' if (hex_str[-1] == 'L'): hex_str = hex_str[0:-1] # Remove trailing 'L' return hex_str # Generate a serializable data dictionary matching the definition: prime_str = num_to_hex_str(self.cryptosystem.get_prime()) generator_str = num_to_hex_str(self.cryptosystem.get_generator()) data = { "PloneVotePrivateKey": { "PrivateKey": num_to_hex_str(self._key), "CryptoSystemScheme": { "nbits": str(self.cryptosystem.get_nbits()), "prime": prime_str, "generator": generator_str } } } # Use the serializer to store the data to file serializer.serialize_to_file(filename, data) @classmethod def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of PrivateKey from the given file. Arguments: filename::string -- The name of a file containing the private key in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored private key file. """ # Create a new serializer object for the PrivateKey structure definition serializer = SerializerClass(PrivateKey_serialize_structure_definition) # Deserialize the PrivateKey instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError, e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid private key. The " \ "following error occurred while trying to deserialize the " \ "file contents: %s" % (filename, str(e))) # Helper function to decode numbers from strings and # raise an exception if the string is not a valid number. # (value_name is used only to construct the exception string). def str_to_num(num_str, base, value_name): try: return int(num_str, base) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid private key. The " \ "stored value for %s is not a valid integer in " \ "base %d representation." % (filename, value_name, base)) # Get the values from the deserialized data inner_elems = data["PloneVotePrivateKey"]["CryptoSystemScheme"] nbits = str_to_num(inner_elems["nbits"], 10, "nbits") prime = str_to_num(inner_elems["prime"], 16, "prime") generator = str_to_num(inner_elems["generator"], 16, "generator") priv_key = str_to_num(data["PloneVotePrivateKey"]["PrivateKey"], 16, "PrivateKey") # Check the loaded values if (not (1 <= priv_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid private key. The value " \ "of the private key given in the file does not match the " \ "indicated cryptosystem. Could the file be corrupt?" % filename) # Construct the cryptosystem object cryptosystem = EGCryptoSystem.load(nbits, prime, generator) # Construct and return the PrivateKey object return cls(cryptosystem, priv_key)
class ThresholdPrivateKey: """ A private key generated in a threshold encryption scheme. Multiple threshold private keys are required in order to decrypt a ciphertext encrypted in a threshold encryption scheme. Because of this, the interface and usage of this class is significantly different from that of PrivateKey (which is why this class is not a subclass of PrivateKey). Note that multiple threshold private keys are associated with each threshold public key, one for each trustee. This again in contrast with simple private/public keys which are paired. Attributes: cryptosystem::EGCryptoSystem -- The ElGamal cryptosystem in which this key is defined. num_trustees::int -- Total number of trustees in the threshold scheme. (the n in "k of n"-decryption) threshold::int -- Minimum number of trustees required to decrypt threshold encrypted messages. (the k in "k of n"-decryption) public_key::ThresholdPublicKey -- The threshold public key to which this threshold private key is associated. """ def __init__(self, cryptosystem, num_trustees, threshold, threshold_public_key, private_key_value): """ Creates a new threshold private key. Should not be invoked directly. Instead of using this constructor from outside of PloneVoteCryptoLib, please use ThresholdEncryptionSetUp.generate_private_key() or ThresholdEncryptionSetUp.generate_key_pair(). Arguments: (see class attributes for cryptosystem, num_trustees and threshold) threshold_public_key::ThresholdPublicKey -- The threshold public key to which this threshold private key is associated. private_key_value::long -- The actual value of the private key (P(j) for trustee j, see ThresholdEncryptionSetUp) """ self.cryptosystem = cryptosystem self.num_trustees = num_trustees self.threshold = threshold self.public_key = threshold_public_key self._key = private_key_value def generate_partial_decryption(self, ciphertext, task_monitor=None, force=False): """ Generates a partial decryption for the given ciphertext. Arguments: ciphertext::Ciphertext -- An encrypted Ciphertext object. task_monitor::TaskMonitor -- A task monitor for this task. force:bool -- Set this to true if you wish to force a decryption attempt, even when the ciphertext's stored public key fingerprint does not match that of the public key associated with this private key. Returns: partial_decryption::PartialDecryption -- A partial decryption of the given ciphertext generated with this threshold private key. Throws: IncompatibleCiphertextError -- The given ciphertext does not appear to be decryptable with the selected private key. """ # Check that the public key fingerprint stored in the ciphertext # matches the public key associated with this private key. if (not force): if (ciphertext.nbits != self.cryptosystem.get_nbits()): raise IncompatibleCiphertextError("The given ciphertext is " \ "not decryptable with the selected private key: " \ "incompatible cryptosystem/key sizes.") if (ciphertext.pk_fingerprint != self.public_key.get_fingerprint()): raise IncompatibleCiphertextError("The given ciphertext is " \ "not decryptable with the selected private key: " \ "public key fingerprint mismatch.") nbits = self.cryptosystem.get_nbits() prime = self.cryptosystem.get_prime() generator = self.cryptosystem.get_generator() key = self._key # Remember that prime is of the form p = 2*q + 1, with q prime. # (By construction, see EGCryptoSystem) q = (prime - 1) / 2 # We will need a random number generator for the proofs of partial # decryption. random = StrongRandom() # New empty partial decryption partial_decryption = PartialDecryption(nbits) # Check if we have a task monitor and register with it if (task_monitor != None): # One tick per block ticks = ciphertext.get_length() partial_decrypt_task_mon = \ task_monitor.new_subtask("Generate partial decryption", expected_ticks = ticks) # For each (gamma,delta) component in the ciphertext, generate one # partial decryption block (with proof): for gamma, delta in ciphertext: # To calculate the value of the block, elevate gamma to the # threshold private key. That is block.value = g^{rP(i)} for each # nbits block of original plaintext. value = pow(gamma, key, prime) # Generate the partial decryption proof for the block as a # Zero-Knowledge Discrete Logarithm Equality Test for # log_{g}(g^{2P(j)}) == log_{gamma}(block^2) # (See PartialDecryptionBlockProof and [TODO: Add reference] for # more information.) # Select a random s in Z_{q}^{*} s = random.randint(1, q - 1) # a = g^{s} mod p a = pow(generator, s, prime) # b = gamma^{s} mod p b = pow(gamma, s, prime) # c is SHA256(a, b, g^{2*P(j)}, block.value) the challenge # (We must use g^{2*P(j)} and not g^{P(j)}, because the first is # considered as the partial public key of trustee j and the value # of the later is unavailable at decryption combination time). sha256 = Crypto.Hash.SHA256.new() sha256.update(hex(a)) sha256.update(hex(b)) sha256.update(hex(pow(generator, 2 * key, prime))) sha256.update(hex(value)) c = int(sha256.hexdigest(), 16) # t = s + 2P(j)*c mod p-1 (P(j): trustee j's threshold private key) # (p - 1 since it is in the exponent and we are already adding the 2 # factor in 2P(j)) t = (s + 2 * key * c) % (prime - 1) # Generate the PartialDecryptionBlockProof as (a, b, t) proof = PartialDecryptionBlockProof(a, b, t) # Generate the block as (value, proof) and add it to the partial # decryption object. block = PartialDecryptionBlock(value, proof) partial_decryption.add_partial_decryption_block(block) # Update task progress if (task_monitor != None): partial_decrypt_task_mon.tick() return partial_decryption def to_file(self, filename, SerializerClass=serialize.XMLSerializer): """ Saves this threshold private key to a file. Arguments: filename::string -- The path to the file in which to store the serialized ThresholdPrivateKey object. SerializerClass::class -- The class that provides the serialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate serialize_to_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) """ # Create a new serializer object for the PrivateKey structure definition serializer = \ SerializerClass(ThresholdPrivateKey_serialize_structure_definition) # Helper function to translate large numbers to their hexadecimal # string representation def num_to_hex_str(num): hex_str = hex(num)[2:] # Remove leading '0x' if (hex_str[-1] == 'L'): hex_str = hex_str[0:-1] # Remove trailing 'L' return hex_str # Generate a serializable data dictionary matching the definition: prime_str = num_to_hex_str(self.cryptosystem.get_prime()) generator_str = num_to_hex_str(self.cryptosystem.get_generator()) verification_data_list = [] for i in range(self.num_trustees): verification_pk_data = { "key": num_to_hex_str(self.public_key._partial_public_keys[i]), "trustee": str(i) } verification_data_list.append(verification_pk_data) data = { "PloneVoteThresholdPrivateKey": { "PrivateKey": num_to_hex_str(self._key), "CryptoSystemScheme": { "nbits": str(self.cryptosystem.get_nbits()), "prime": prime_str, "generator": generator_str }, "ThresholdKeyInfo": { "NumTrustees": str(self.num_trustees), "Threshold": str(self.threshold), "ThresholdPublicKey": num_to_hex_str(self.public_key._key), "PartialPublicKey": verification_data_list } } } # Use the serializer to store the data to file serializer.serialize_to_file(filename, data) @classmethod def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of ThresholdPrivateKey from the given file. Arguments: filename::string -- The name of a file containing the threshold public key in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored public key file. """ # Create a new serializer object for the PublicKey structure definition serializer = SerializerClass( ThresholdPrivateKey_serialize_structure_definition) # Deserialize the ThresholdPrivateKey instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError, e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private key. " \ "The following error occurred while trying to deserialize " \ "the file contents: %s" % (filename, str(e))) # Helper function to decode numbers from strings and # raise an exception if the string is not a valid number. # (value_name is used only to construct the exception string). def str_to_num(num_str, base, value_name): try: return int(num_str, base) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private " \ "key. The stored value for %s is not a valid integer in " \ "base %d representation." % (filename, value_name, base)) # Get the values from the deserialized data inner_elems = data["PloneVoteThresholdPrivateKey"][ "CryptoSystemScheme"] nbits = str_to_num(inner_elems["nbits"], 10, "nbits") prime = str_to_num(inner_elems["prime"], 16, "prime") generator = str_to_num(inner_elems["generator"], 16, "generator") prv_key = str_to_num( data["PloneVoteThresholdPrivateKey"]["PrivateKey"], 16, "PrivateKey") threshold_info = \ data["PloneVoteThresholdPrivateKey"]["ThresholdKeyInfo"] num_trustees = \ str_to_num(threshold_info["NumTrustees"], 10, "NumTrustees") threshold = \ str_to_num(threshold_info["Threshold"], 10, "Threshold") pub_key = str_to_num(threshold_info["ThresholdPublicKey"], 16, "PublicKey") pp_keys = threshold_info["PartialPublicKey"] partial_public_keys = [None for o in pp_keys] for pp_key in pp_keys: trustee = str_to_num(pp_key["trustee"], 10, "trustee") key_val = str_to_num(pp_key["key"], 16, "key") partial_public_keys[trustee] = key_val # Check the loaded values if (not (1 <= prv_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private key."\ "The value of the private key given in the file does" \ "not match the indicated cryptosystem. Could the file be" \ "corrupt?" % filename) if (not (1 <= pub_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private key."\ "The value of the threshold public key given in the file does" \ "not match the indicated cryptosystem. Could the file be" \ "corrupt?" % filename) for pp_key in partial_public_keys: if (not (1 <= pp_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private "\ "key. The value of at least one of the partial public " \ "keys given in the file does not match the indicated " \ "cryptosystem. Could the file be corrupt?" % filename) # Construct the cryptosystem object cryptosystem = EGCryptoSystem.load(nbits, prime, generator) # Contruct the Threshold Public Key threshold_public_key = ThresholdPublicKey(cryptosystem, num_trustees, threshold, pub_key, partial_public_keys) # Construct and return the ThresholdPrivateKey object return cls(cryptosystem, num_trustees, threshold, threshold_public_key, prv_key)
def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of PrivateKey from the given file. Arguments: filename::string -- The name of a file containing the private key in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored private key file. """ # Create a new serializer object for the PrivateKey structure definition serializer = SerializerClass(PrivateKey_serialize_structure_definition) # Deserialize the PrivateKey instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError as e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid private key. The " \ "following error occurred while trying to deserialize the " \ "file contents: %s" % (filename, str(e))) # Helper function to decode numbers from strings and # raise an exception if the string is not a valid number. # (value_name is used only to construct the exception string). def str_to_num(num_str, base, value_name): try: return int(num_str, base) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid private key. The " \ "stored value for %s is not a valid integer in " \ "base %d representation." % (filename, value_name, base)) # Get the values from the deserialized data inner_elems = data["PloneVotePrivateKey"]["CryptoSystemScheme"] nbits = str_to_num(inner_elems["nbits"], 10, "nbits") prime = str_to_num(inner_elems["prime"], 16, "prime") generator = str_to_num(inner_elems["generator"], 16, "generator") priv_key = str_to_num(data["PloneVotePrivateKey"]["PrivateKey"], 16, "PrivateKey") # Check the loaded values if (not (1 <= priv_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid private key. The value " \ "of the private key given in the file does not match the " \ "indicated cryptosystem. Could the file be corrupt?" % filename) # Construct the cryptosystem object cryptosystem = EGCryptoSystem.load(nbits, prime, generator) # Construct and return the PrivateKey object return cls(cryptosystem, priv_key)
class Ciphertext: """ An object representing encrypted PloneVote data. Ciphertext objects are created by PublicKey encrypt operations and decrypted through PrivateKey decrypt methods (or through ThresholdDecryptionCombinator if the data was encrypted with a threshold public key and all partial decryptions are available). This class can also be store to and loaded from file using the PloneVote armored ciphertext XML format. Attributes: nbits::int -- Size in bits of the cryptosystem/public key used to encrypt this ciphertext. pk_fingerprint::string -- A fingerprint of the public key used to encrypt this ciphertext. This fingerprint can then be compared with the result from PublicKey.get_fingerprint() to check for compatibility with a given key pair or threshold public key. gamma::long[] delta::long[] -- : This two attributes should only be accessed by key classes within PloneVoteCryptoLib. See "Handbook of Applied Cryptography" Algorithm 8.18 for the meaning of the variables. An array is used because the encrypted data might be longer than the cryptosystem's bit size. """ def get_length(self): """ Returns the length, in blocks, of the ciphertext. """ assert len(self.gamma) == len(self.delta), "Each gamma component of " \ "the ciphertext must correspond " \ " to one delta component." return len(self.gamma) def __getitem__(self, i): """ Makes this object indexable. Returns: (gamma, delta)::(long, long) -- Returns the gamma, delta pair representing a particular block of the encrypted data. Use ciphertext[i] for block i. """ return (self.gamma[i], self.delta[i]) def __iter__(self): """ Return an iterator (CiphertextIterator) for the current ciphertext. """ return CiphertextIterator(self) def __eq__(self, other): """ Implements Ciphertext equality. Two ciphertexts are equal if they have the same bit size, public key fingerprint and list of gamma and delta components. A ciphertext is not equal to any object of a different type. """ if (isinstance(other, Ciphertext) and (other.nbits == self.nbits) and (other.pk_fingerprint == self.pk_fingerprint) and (other.gamma == self.gamma) and (other.delta == self.delta)): return True else: return False def __ne__(self, other): """ Implements Ciphertext inequality. See __eq__(...) for the definition of Ciphertext equality, inequality its is negation. """ return not self.__eq__(other) def get_fingerprint(self): """ Gets a fingerprint of the current ciphertext. A ciphertext fingerprint is generated as a SHA-256 hash of the ciphertext, block by block. Returns: fingerprint::string -- A SHA-256 hexdigest providing a fingerprint of the current ciphertext. """ fingerprint = Crypto.Hash.SHA256.new() for (gamma, delta) in self: fingerprint.update(hex(gamma)) fingerprint.update(hex(delta)) return fingerprint.hexdigest() def __init__(self, nbits, public_key_fingerprint): """ Create an empty ciphertext object. Arguments: nbits::int -- Size in bits of the cryptosystem/public key used to encrypt this ciphertext. public_key_fingerprint::string -- The fingerprint of the public key used to encrypt this data. """ self.gamma = [] self.delta = [] self.nbits = nbits self.pk_fingerprint = public_key_fingerprint def append(self, gamma, delta): """ Used internally by PublicKey. This method adds an encrypted block of data with its gamma and delta components from ElGamal (see HoAC Alg. 8.18). """ self.gamma.append(gamma) self.delta.append(delta) def _encrypted_data_as_bitstream(self): """ Returns the contents of this ciphertext as a BitStream object. This includes only the encrypted data (gamma and delta components), not the nbits and public key fingerprint metadata. The components are encoded alternating as follows: [gamma[0], delta[0], gamma[1], delta[1], ...] with each component represented as a nbits long number. Returns: bitstream::BitStream -- The gamma and delta components of this ciphertext as a bitstream. """ bitstream = BitStream() for i in range(0, self.get_length()): bitstream.put_num(self.gamma[i], self.nbits) bitstream.put_num(self.delta[i], self.nbits) return bitstream def _encrypted_data_as_base64(self): """ Returns the contents of this ciphertext as a base64 string. This includes only the encrypted data (gamma and delta components), not the nbits and public key fingerprint metadata. """ bitstream = self._encrypted_data_as_bitstream() bitstream.seek(0) length = bitstream.get_length() assert length % 8 == 0, \ "The ciphertext data must be a multiple of eight bits in size." return bitstream.get_base64(length) def to_file(self, filename, SerializerClass=serialize.XMLSerializer): """ Saves this ciphertext to a file. Arguments: filename::string -- The path to the file in which to store the serialized Ciphertext object. SerializerClass::class -- The class that provides the serialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate serialize_to_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) """ # Create a new serializer object for the Ciphertext structure definition serializer = SerializerClass(Ciphertext_serialize_structure_definition) # Generate a serializable data dictionary matching the definition: data = { "PloneVoteCiphertext": { "nbits": str(self.nbits), "PKFingerprint": self.pk_fingerprint, "EncryptedData": self._encrypted_data_as_base64() } } # Use the serializer to store the data to file serializer.serialize_to_file(filename, data) @classmethod def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of Ciphertext from the given file. Arguments: filename::string -- The name of a file containing the ciphertext in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored ciphertext file. """ # Create a new serializer object for the Ciphertext structure definition serializer = SerializerClass(Ciphertext_serialize_structure_definition) # Deserialize the Ciphertext instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError, e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid ciphertext. The " \ "following error occurred while trying to deserialize the " \ "file contents: %s" % (filename, str(e))) # Get the values from the deserialized data try: nbits = int(data["PloneVoteCiphertext"]["nbits"]) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid ciphertext. The " \ "stored value for nbits is not a valid (decimal) integer." \ % filename) fingerprint_str = data["PloneVoteCiphertext"]["PKFingerprint"] enc_data_str = data["PloneVoteCiphertext"]["EncryptedData"] # Construct a new Ciphertext object with the given nbits and fingerprint ciphertext = cls(nbits, fingerprint_str) # Load the encrypted data bitstream = BitStream() bitstream.put_base64(enc_data_str) bitstream.seek(0) length = bitstream.get_length() # number of gamma and delta blocks in the bitstream: blocks = length / (nbits * 2) for i in range(0, blocks): gamma_val = bitstream.get_num(nbits) delta_val = bitstream.get_num(nbits) ciphertext.append(gamma_val, delta_val) # Return the ciphertext return ciphertext
def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of Ciphertext from the given file. Arguments: filename::string -- The name of a file containing the ciphertext in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored ciphertext file. """ # Create a new serializer object for the Ciphertext structure definition serializer = SerializerClass(Ciphertext_serialize_structure_definition) # Deserialize the Ciphertext instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError as e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid ciphertext. The " \ "following error occurred while trying to deserialize the " \ "file contents: %s" % (filename, str(e))) # Get the values from the deserialized data try: nbits = int(data["PloneVoteCiphertext"]["nbits"]) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid ciphertext. The " \ "stored value for nbits is not a valid (decimal) integer." \ % filename) fingerprint_str = data["PloneVoteCiphertext"]["PKFingerprint"] enc_data_str = data["PloneVoteCiphertext"]["EncryptedData"] # Construct a new Ciphertext object with the given nbits and fingerprint ciphertext = cls(nbits, fingerprint_str) # Load the encrypted data bitstream = BitStream() bitstream.put_base64(enc_data_str) bitstream.seek(0) length = bitstream.get_length() # number of gamma and delta blocks in the bitstream: blocks = length // (nbits * 2) for i in range(0, blocks): gamma_val = bitstream.get_num(nbits) delta_val = bitstream.get_num(nbits) ciphertext.append(gamma_val, delta_val) # Return the ciphertext return ciphertext
def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of ThresholdPrivateKey from the given file. Arguments: filename::string -- The name of a file containing the threshold public key in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored public key file. """ # Create a new serializer object for the PublicKey structure definition serializer = SerializerClass( ThresholdPrivateKey_serialize_structure_definition) # Deserialize the ThresholdPrivateKey instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError as e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private key. " \ "The following error occurred while trying to deserialize " \ "the file contents: %s" % (filename, str(e))) # Helper function to decode numbers from strings and # raise an exception if the string is not a valid number. # (value_name is used only to construct the exception string). def str_to_num(num_str, base, value_name): try: return int(num_str, base) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private " \ "key. The stored value for %s is not a valid integer in " \ "base %d representation." % (filename, value_name, base)) # Get the values from the deserialized data inner_elems = data["PloneVoteThresholdPrivateKey"][ "CryptoSystemScheme"] nbits = str_to_num(inner_elems["nbits"], 10, "nbits") prime = str_to_num(inner_elems["prime"], 16, "prime") generator = str_to_num(inner_elems["generator"], 16, "generator") prv_key = str_to_num( data["PloneVoteThresholdPrivateKey"]["PrivateKey"], 16, "PrivateKey") threshold_info = \ data["PloneVoteThresholdPrivateKey"]["ThresholdKeyInfo"] num_trustees = \ str_to_num(threshold_info["NumTrustees"], 10, "NumTrustees") threshold = \ str_to_num(threshold_info["Threshold"], 10, "Threshold") pub_key = str_to_num(threshold_info["ThresholdPublicKey"], 16, "PublicKey") pp_keys = threshold_info["PartialPublicKey"] partial_public_keys = [None for o in pp_keys] for pp_key in pp_keys: trustee = str_to_num(pp_key["trustee"], 10, "trustee") key_val = str_to_num(pp_key["key"], 16, "key") partial_public_keys[trustee] = key_val # Check the loaded values if (not (1 <= prv_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private key."\ "The value of the private key given in the file does" \ "not match the indicated cryptosystem. Could the file be" \ "corrupt?" % filename) if (not (1 <= pub_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private key."\ "The value of the threshold public key given in the file does" \ "not match the indicated cryptosystem. Could the file be" \ "corrupt?" % filename) for pp_key in partial_public_keys: if (not (1 <= pp_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold private "\ "key. The value of at least one of the partial public " \ "keys given in the file does not match the indicated " \ "cryptosystem. Could the file be corrupt?" % filename) # Construct the cryptosystem object cryptosystem = EGCryptoSystem.load(nbits, prime, generator) # Contruct the Threshold Public Key threshold_public_key = ThresholdPublicKey(cryptosystem, num_trustees, threshold, pub_key, partial_public_keys) # Construct and return the ThresholdPrivateKey object return cls(cryptosystem, num_trustees, threshold, threshold_public_key, prv_key)
class PublicKey: """ An ElGamal public key object used for encryption. Attributes: cryptosystem::EGCryptoSystem -- The ElGamal cryptosystem in which this key is defined. """ def get_fingerprint(self): """ Gets a fingerprint of the current public key. This fingerprint should be stored with any text encrypted with this public key, in order to facilitate checking compatibility with a particular key pair for future decryption or manipulation. Returns: fingerprint::string -- A SHA256 hexdigest providing a fingerprint of the current public key. """ fingerprint = Crypto.Hash.SHA256.new() fingerprint.update(hex(self.cryptosystem.get_nbits())) fingerprint.update(hex(self.cryptosystem.get_prime())) fingerprint.update(hex(self.cryptosystem.get_generator())) fingerprint.update(hex(self._key)) return fingerprint.hexdigest() def __eq__(self, other): """ Implements PublicKey equality. """ if (isinstance(other, PublicKey) and (other.cryptosystem == self.cryptosystem) and (other._key == self._key)): return True else: return False def __ne__(self, other): """ Implements PublicKey inequality. """ return not self.__eq__(other) def __init__(self, cryptosystem, public_key_value): """ Creates a new public key. Should not be invoked directly. Instead of using this constructor from outside of PloneVoteCryptoLib, please use the class methods EGCryptoSystem.new_key_pair() or PublicKey.from_file(file). Arguments: cryptosystem::EGCryptoSystem-- The ElGamal cryptosystem in which this key is defined. public_key_value::long -- The actual value of the public key (g^a mod p, where a is the priv. key) """ self.cryptosystem = cryptosystem self._key = public_key_value def encrypt_bitstream(self, bitstream, pad_to=None, task_monitor=None): """ Encrypts the given bitstream into a ciphertext object. Arguments: bitstream::BitStream-- A stream of bits to encrypt (see BitStream utility class). pad_to::int -- Minimum size (in bytes) of the resulting ciphertext. Data will be padded before encryption to match this size. task_monitor::TaskMonitor -- A task monitor for this task. Returns: ciphertext:Ciphertext -- A ciphertext object encapsulating the encrypted data. """ random = StrongRandom() ## PART 1 # First, format the bitstream as per Ciphertext.py Note 001, # previous to encryption. # [size (64 bits) | message (size bits) | padding (X bits) ] ## formated_bitstream = BitStream() # The first 64 encode the size of the actual data in bits SIZE_BLOCK_LENGTH = 64 size_in_bits = bitstream.get_length() if (size_in_bits >= 2**SIZE_BLOCK_LENGTH): raise ValueError("The size of the bitstream to encrypt is larger " \ "than 16 Exabits. The current format for " \ "PloneVote ciphertext only allows encrypting a " \ "maximum of 16 Exabits of information.") formated_bitstream.put_num(size_in_bits, SIZE_BLOCK_LENGTH) # We then copy the contents of the original bitstream bitstream.seek(0) formated_bitstream.put_bitstream_copy(bitstream) # Finally, we append random data until we reach the desired pad_to # length unpadded_length = formated_bitstream.get_length() if (pad_to != None and (pad_to * 8) > unpadded_length): full_length = pad_to * 8 else: full_length = unpadded_length padding_left = full_length - unpadded_length while (padding_left > 1024): padding_bits = random.randint(1, 2**1024) formated_bitstream.put_num(padding_bits, 1024) padding_left -= 1024 if (padding_left > 0): padding_bits = random.randint(1, 2**padding_left) formated_bitstream.put_num(padding_bits, padding_left) padding_left = 0 ## PART 2 # We encrypt the formated bitsteam using ElGamal into a Ciphertext # object. # See "Handbook of Applied Cryptography" Algorithm 8.18 ## # block_size is the size of each block of bits to encrypt # since we can only encrypt messages in [0, p - 1] # we should use (nbits - 1) as the block size, where # 2**(nbits - 1) < p < 2**nbits block_size = self.cryptosystem.get_nbits() - 1 prime = self.cryptosystem.get_prime() generator = self.cryptosystem.get_generator() # We pull data from the bitstream one block at a time and encrypt it formated_bitstream.seek(0) ciphertext = \ Ciphertext(self.cryptosystem.get_nbits(), self.get_fingerprint()) plaintext_bits_left = formated_bitstream.get_length() # Check if we have a task monitor and register with it if (task_monitor != None): # We will do two tick()s per block to encrypt: one for generating # the gamma component of the ciphertext block and another for the # delta component (those are the two time intensive steps, # because of exponentiation). ticks = math.ceil((1.0 * plaintext_bits_left) / block_size) * 2 encrypt_task_mon = \ task_monitor.new_subtask("Encrypt data", expected_ticks = ticks) while (plaintext_bits_left > 0): # get next block (message, m, etc) to encrypt if (plaintext_bits_left >= block_size): block = formated_bitstream.get_num(block_size) plaintext_bits_left -= block_size else: block = formated_bitstream.get_num(plaintext_bits_left) # Encrypt as if the stream was filled with random data past its # end, this avoids introducing a 0's gap during decryption to # bitstream displacement = block_size - plaintext_bits_left block = block << displacement padding = random.randint(0, 2**displacement - 1) assert (padding / 2**displacement == 0), \ "padding should be at most displacement bits long" block = block | padding plaintext_bits_left = 0 # Select a random integer k, 1 <= k <= p − 2 k = random.randint(1, prime - 2) # Compute gamma and delta gamma = pow(generator, k, prime) if (task_monitor != None): encrypt_task_mon.tick() delta = (block * pow(self._key, k, prime)) % prime if (task_monitor != None): encrypt_task_mon.tick() # Add this encrypted data portion to the ciphertext object ciphertext.append(gamma, delta) # return the ciphertext object return ciphertext def encrypt_text(self, text, pad_to=None, task_monitor=None): """ Encrypts the given string into a ciphertext object. Arguments: text::string -- A string to encrypt. pad_to::int -- Minimum size (in bytes) of the resulting ciphertext. Data will be padded before encryption to match this size. task_monitor::TaskMonitor -- A task monitor for this task. Returns: ciphertext:Ciphertext -- A ciphertext object encapsulating the encrypted data. """ bitstream = BitStream() bitstream.put_string(text) return self.encrypt_bitstream(bitstream, pad_to, task_monitor) def to_file(self, filename, SerializerClass=serialize.XMLSerializer): """ Saves this public key to a file. Arguments: filename::string -- The path to the file in which to store the serialized PublicKey object. SerializerClass::class -- The class that provides the serialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate serialize_to_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) """ # Create a new serializer object for the PublicKey structure definition serializer = SerializerClass(PublicKey_serialize_structure_definition) # Helper function to translate large numbers to their hexadecimal # string representation def num_to_hex_str(num): hex_str = hex(num)[2:] # Remove leading '0x' if (hex_str[-1] == 'L'): hex_str = hex_str[0:-1] # Remove trailing 'L' return hex_str # Generate a serializable data dictionary matching the definition: prime_str = num_to_hex_str(self.cryptosystem.get_prime()) generator_str = num_to_hex_str(self.cryptosystem.get_generator()) data = { "PloneVotePublicKey": { "PublicKey": num_to_hex_str(self._key), "CryptoSystemScheme": { "nbits": str(self.cryptosystem.get_nbits()), "prime": prime_str, "generator": generator_str } } } # Use the serializer to store the data to file serializer.serialize_to_file(filename, data) @classmethod def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of PublicKey from the given file. Arguments: filename::string -- The name of a file containing the public key in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored public key file. """ # Create a new serializer object for the PublicKey structure definition serializer = SerializerClass(PublicKey_serialize_structure_definition) # Deserialize the PublicKey instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError, e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid public key. The " \ "following error occurred while trying to deserialize the " \ "file contents: %s" % (filename, str(e))) # Verify that we are dealing with a single public key and not a # threshold public key. In the later case, call # ThresholdPublicKey.from_file on the given file, instead of this # method. if (data["PloneVotePublicKey"].has_key("ThresholdKeyInfo")): from plonevotecryptolib.Threshold.ThresholdPublicKey import \ ThresholdPublicKey return ThresholdPublicKey.from_file(filename, SerializerClass) # Helper function to decode numbers from strings and # raise an exception if the string is not a valid number. # (value_name is used only to construct the exception string). def str_to_num(num_str, base, value_name): try: return int(num_str, base) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid public key. The " \ "stored value for %s is not a valid integer in " \ "base %d representation." % (filename, value_name, base)) # Get the values from the deserialized data inner_elems = data["PloneVotePublicKey"]["CryptoSystemScheme"] nbits = str_to_num(inner_elems["nbits"], 10, "nbits") prime = str_to_num(inner_elems["prime"], 16, "prime") generator = str_to_num(inner_elems["generator"], 16, "generator") pub_key = str_to_num(data["PloneVotePublicKey"]["PublicKey"], 16, "PublicKey") # Check the loaded values if (not (1 <= pub_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid public key. The value " \ "of the public key given in the file does not match the " \ "indicated cryptosystem. Could the file be corrupt?" % filename) # Construct the cryptosystem object cryptosystem = EGCryptoSystem.load(nbits, prime, generator) # Construct and return the PublicKey object return cls(cryptosystem, pub_key)
class ThresholdPublicKey(PublicKey): """ A public key generated in a threshold encryption scheme. This class is compatible with the PublicKey class and inherits from it. It adds some metadata about the threshold encryption scheme and changes its format on file slightly, but otherwise it presents the same methods and properties that PublicKey and can be used to encrypt data without any knowledge of the threshold decryption or key set-up process. Attributes: cryptosystem::EGCryptoSystem -- The ElGamal cryptosystem in which this key is defined. num_trustees::int -- Total number of trustees in the threshold scheme. (the n in "k of n"-decryption) threshold::int -- Minimum number of trustees required to decrypt threshold encrypted messages. (the k in "k of n"-decryption) """ def get_fingerprint(self): # We override this PublicKey method to add partial public keys to the # input of the hash function to create the fingerprint. fingerprint = Crypto.Hash.SHA256.new() fingerprint.update(hex(self.cryptosystem.get_nbits())) fingerprint.update(hex(self.cryptosystem.get_prime())) fingerprint.update(hex(self.cryptosystem.get_generator())) fingerprint.update(hex(self._key)) for partial_public_key in self._partial_public_keys: fingerprint.update(hex(partial_public_key)) return fingerprint.hexdigest() def get_partial_public_key(self, trustee): """ Retrieve the partial public key for the given trustee. The partial public key for trustee i is g^P(i). This value is used for verification of the partial decryptions created by said trustee. Instead of using this values from outside of PloneVoteCryptoLib, please use ThresholdDecryptionCombinator to verify and combine partial decryptions. Arguments: trustee::int -- The number of the trustee for which we wish to obtain the partial public key. """ return self._partial_public_keys[trustee] def __init__(self, cryptosystem, num_trustees, threshold, public_key_value, verification_partial_public_keys): """ Creates a new threshold public key. Should not be invoked directly. Instead of using this constructor from outside of PloneVoteCryptoLib, please use ThresholdEncryptionSetUp.generate_public_key(). Arguments: (see class attributes for cryptosystem, num_trustees and threshold) public_key_value::long -- The actual value of the public key (g^2P(0) mod p, see ThresholdEncryptionSetUp) verification_partial_public_keys::long[] -- A series of "partial public keys" (g^P(i) for each trustee i), used for partial decryption verification. Note that the key for trustee i must be on index i-1 of the array. """ PublicKey.__init__(self, cryptosystem, public_key_value) # Some checks: if (threshold > num_trustees): raise ValueError("Invalid parameters for the threshold public key:"\ " threshold must be smaller than the total number"\ " of trustees. Got num_trustees=%d, threshold=%d" \ % (num_trustees, threshold)) if (len(verification_partial_public_keys) != num_trustees): raise ValueError("Invalid parameters for the threshold public key:"\ " a verification partial public for each trustee "\ "must be included.") self.num_trustees = num_trustees self.threshold = threshold self._partial_public_keys = verification_partial_public_keys def to_file(self, filename, SerializerClass=serialize.XMLSerializer): """ Saves this threshold public key to a file. Arguments: filename::string -- The path to the file in which to store the serialized ThresholdPublicKey object. SerializerClass::class -- The class that provides the serialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate serialize_to_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) """ # Create a new serializer object for the PublicKey structure definition serializer = SerializerClass(PublicKey_serialize_structure_definition) # Helper function to translate large numbers to their hexadecimal # string representation def num_to_hex_str(num): hex_str = hex(num)[2:] # Remove leading '0x' if (hex_str[-1] == 'L'): hex_str = hex_str[0:-1] # Remove trailing 'L' return hex_str # Generate a serializable data dictionary matching the definition: prime_str = num_to_hex_str(self.cryptosystem.get_prime()) generator_str = num_to_hex_str(self.cryptosystem.get_generator()) verification_data_list = [] for i in range(self.num_trustees): verification_pk_data = { "key": num_to_hex_str(self._partial_public_keys[i]), "trustee": str(i) } verification_data_list.append(verification_pk_data) data = { "PloneVotePublicKey": { "PublicKey": num_to_hex_str(self._key), "CryptoSystemScheme": { "nbits": str(self.cryptosystem.get_nbits()), "prime": prime_str, "generator": generator_str }, "ThresholdKeyInfo": { "NumTrustees": str(self.num_trustees), "Threshold": str(self.threshold), "PartialPublicKey": verification_data_list } } } # Use the serializer to store the data to file serializer.serialize_to_file(filename, data) @classmethod def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of ThresholdPublicKey from the given file. Arguments: filename::string -- The name of a file containing the threshold public key in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored public key file. """ # Create a new serializer object for the PublicKey structure definition serializer = SerializerClass(PublicKey_serialize_structure_definition) # Deserialize the ThresholdPublicKey instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError, e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold public key. " \ "The following error occurred while trying to deserialize " \ "the file contents: %s" % (filename, str(e))) # Verify that we are dealing with a threshold public key and not a # single public key. if(not \ data["PloneVotePublicKey"].has_key("ThresholdKeyInfo")): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold public key. " \ "Instead it contains a single (non-threshold) public key" \ % filename) # Helper function to decode numbers from strings and # raise an exception if the string is not a valid number. # (value_name is used only to construct the exception string). def str_to_num(num_str, base, value_name): try: return int(num_str, base) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid threshold public " \ "key. The stored value for %s is not a valid integer in " \ "base %d representation." % (filename, value_name, base)) # Get the values from the deserialized data inner_elems = data["PloneVotePublicKey"]["CryptoSystemScheme"] nbits = str_to_num(inner_elems["nbits"], 10, "nbits") prime = str_to_num(inner_elems["prime"], 16, "prime") generator = str_to_num(inner_elems["generator"], 16, "generator") pub_key = str_to_num(data["PloneVotePublicKey"]["PublicKey"], 16, "PublicKey") threshold_info = data["PloneVotePublicKey"]["ThresholdKeyInfo"] num_trustees = \ str_to_num(threshold_info["NumTrustees"], 10, "NumTrustees") threshold = \ str_to_num(threshold_info["Threshold"], 10, "Threshold") pp_keys = threshold_info["PartialPublicKey"] partial_public_keys = [None for o in pp_keys] for pp_key in pp_keys: trustee = str_to_num(pp_key["trustee"], 10, "trustee") key_val = str_to_num(pp_key["key"], 16, "key") partial_public_keys[trustee] = key_val # Check the loaded values if (not (1 <= pub_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid public key. The value " \ "of the public key given in the file does not match the " \ "indicated cryptosystem. Could the file be corrupt?" % filename) for pp_key in partial_public_keys: if (not (1 <= pp_key <= prime - 2)): raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid public key. The " \ "value of at least one of the partial public keys given " \ "in the file does not match the indicated cryptosystem. " \ "Could the file be corrupt?" % filename) # Construct the cryptosystem object cryptosystem = EGCryptoSystem.load(nbits, prime, generator) # Construct and return the PublicKey object return cls(cryptosystem, num_trustees, threshold, pub_key, partial_public_keys)