def _get_generator(p, task_monitor=None):
        """
        Returns the generator of the Z_{p}^{*} cyclic group.
        
        We take random numbers in Z_{p}^{*} = [0, ..., p - 1], until one of  
        them is a generator for the group. This function assumes that p is a 
        safe prime (p = 2q + 1 with both p and q prime).
        
        See the documentation for _is_generator(p, g) for more information 
        about testing whether a number is a generator of Z_{p}^{*}.
        
        Arguments:
            p::long    -- A safe prime.
            task_monitor::TaskMonitor    -- A task monitor for the process.
        
        Returns:
            g::long    -- A generator of Z_{p}^{*}
        """        
        random = StrongRandom()
        candidate = random.randint(1, p - 1)
        if(task_monitor != None): task_monitor.tick()
        
        while(not _is_generator(p, candidate)):
            candidate = random.randint(1, p - 1)
            if(task_monitor != None): task_monitor.tick()
        
        if(params.DEBUG):
            assert pow(candidate, p - 1, p) == 1, \
                   "generator^{p-1} != 1 mod p (!) see method's " \
                   "algorithm explanation."
        
        return candidate # this is the generator
Exemplo n.º 2
0
def _get_generator(p, task_monitor=None):
    """
        Returns the generator of the Z_{p}^{*} cyclic group.
        
        We take random numbers in Z_{p}^{*} = [0, ..., p - 1], until one of  
        them is a generator for the group. This function assumes that p is a 
        safe prime (p = 2q + 1 with both p and q prime).
        
        See the documentation for _is_generator(p, g) for more information 
        about testing whether a number is a generator of Z_{p}^{*}.
        
        Arguments:
            p::long    -- A safe prime.
            task_monitor::TaskMonitor    -- A task monitor for the process.
        
        Returns:
            g::long    -- A generator of Z_{p}^{*}
        """
    random = StrongRandom()
    candidate = random.randint(1, p - 1)
    if (task_monitor != None): task_monitor.tick()

    while (not _is_generator(p, candidate)):
        candidate = random.randint(1, p - 1)
        if (task_monitor != None): task_monitor.tick()

    if (params.DEBUG):
        assert pow(candidate, p - 1, p) == 1, \
               "generator^{p-1} != 1 mod p (!) see method's " \
               "algorithm explanation."

    return candidate  # this is the generator
    def get_challenge(self):
        '''
        Generate a QR2Auth challenge.
        A QR2Auth challenge consists of 128 random bits generated by PyCrypto
        with StrongRandom.
        These random bits are hashed with SHA512. This hash value represents
        the challenge.

        :return: A tuple containing the QR2Auth challenge as well as the range
                 of the OTP in the response hash value.
        :rtype: tuple
        '''
        random_pool = StrongRandom()
        nonce = random_pool.getrandbits(128)
        nonce_hash = SHA512.new(str(nonce)).hexdigest()
        self.__start = int(random_pool.randint(0, 128))
        '''
        Start and end of the range must be between 0 and the length of the hash
        We use Sha512, so in this case start and end must be between
        0 and 128
        '''
        self.__end = self.__start + self.__otp_length
        if self.__end > len(nonce_hash):
            self.__end = self.__end - len(nonce_hash)
        self.__challenge = nonce_hash
        return self.__challenge, self.__start, self.__end
Exemplo n.º 4
0
 def __init__(self, cryptosystem):
     """
     Generates a new key pair for the given EGCryptoSystem
     """
     p = cryptosystem.get_prime()
     random = StrongRandom()
     
     inner_private_key = random.randint(1, p - 2)
     
     self.private_key = PrivateKey(cryptosystem, inner_private_key)
     self.public_key = self.private_key.public_key
Exemplo n.º 5
0
    def __init__(self, cryptosystem):
        """
        Generates a new key pair for the given EGCryptoSystem
        """
        p = cryptosystem.get_prime()
        random = StrongRandom()

        inner_private_key = random.randint(1, p - 2)

        self.private_key = PrivateKey(cryptosystem, inner_private_key)
        self.public_key = self.private_key.public_key
Exemplo n.º 6
0
    def sign(self, msg_hash):
        """Produce the DSS signature of a message.

        :Parameters:
          msg_hash : hash object
            The hash that was carried out over the message.
            The object belongs to the `Crypto.Hash` package.

            Under mode *'fips-186-3'*, the hash must be a FIPS
            approved secure hash (SHA-1 or a member of the SHA-2 family).

        :Return: The signature encoded as a byte string.
        :Raise ValueError:
            If the hash algorithm is incompatible to the DSA key.
        :Raise TypeError:
            If the DSA key has no private half.
        """

        # Generate the nonce k (critical!)
        if self._deterministic:
            nonce = self._compute_nonce(msg_hash)
        else:
            if self._n > msg_hash.digest_size * 8:
                raise ValueError("Hash is not long enough")

            if not msg_hash.name.upper().startswith("SHA"):
                raise ValueError("Hash %s does not belong to SHS" %
                                 msg_hash.name)

            rng = StrongRandom(randfunc=self._randfunc)
            nonce = rng.randint(1, self._key.q - 1)

        # Perform signature using the raw API
        z = bytes_to_long(msg_hash.digest()[:self._n])
        sig_pair = self._key._sign(z, nonce)

        # Encode the signature into a single byte string
        if self._encoding == 'binary':
            output = b("").join([long_to_bytes(x, self._n) for x in sig_pair])
        else:
            # Dss-sig  ::=  SEQUENCE  {
            #               r       OCTET STRING,
            #               s       OCTET STRING
            # }
            der_seq = DerSequence(sig_pair)
            output = der_seq.encode()

        return output
Exemplo n.º 7
0
    def sign(self, msg_hash):
        """Produce the DSS signature of a message.

        :Parameters:
          msg_hash : hash object
            The hash that was carried out over the message.
            The object belongs to the `Crypto.Hash` package.

            Under mode *'fips-186-3'*, the hash must be a FIPS
            approved secure hash (SHA-1 or a member of the SHA-2 family).

        :Return: The signature encoded as a byte string.
        :Raise ValueError:
            If the hash algorithm is incompatible to the DSA key.
        :Raise TypeError:
            If the DSA key has no private half.
        """

        # Generate the nonce k (critical!)
        if self._deterministic:
            nonce = self._compute_nonce(msg_hash)
        else:
            if self._n > msg_hash.digest_size * 8:
                raise ValueError("Hash is not long enough")

            if not hash_is_shs(msg_hash):
                raise ValueError("Hash does not belong to SHS")

            rng = StrongRandom(randfunc=self._randfunc)
            nonce = rng.randint(1, self._key.q - 1)

        # Perform signature using the raw API
        z = bytes_to_long(msg_hash.digest()[:self._n])
        sig_pair = self._key._sign(z, nonce)

        # Encode the signature into a single byte string
        if self._encoding == 'binary':
            output = b("").join([long_to_bytes(x, self._n)
                                 for x in sig_pair])
        else:
            # Dss-sig  ::=  SEQUENCE  {
            #               r       OCTET STRING,
            #               s       OCTET STRING
            # }
            der_seq = DerSequence(sig_pair)
            output = der_seq.encode()

        return output
Exemplo n.º 8
0
	def new_random_polynomial(cls, modulus, degree):
		"""
		Construct a new polynomial of the given degree with random coefficients.
		
		Arguments:
			modulus::long	-- All arithmetic for this polynomial will be 
							   performed with this modulus. 
							   That is, in the Z_{modulus} multiplicative group.
			degree::int		-- Degree of the new polynomial.
 		"""
		coefficients = []
		random = StrongRandom()
		for i in range(0, degree + 1):
			coeff =  random.randint(1, modulus - 1)
			coefficients.append(coeff)
		
		return cls(modulus, coefficients)
Exemplo n.º 9
0
    def new_random_polynomial(cls, modulus, degree):
        """
		Construct a new polynomial of the given degree with random coefficients.
		
		Arguments:
			modulus::long	-- All arithmetic for this polynomial will be 
							   performed with this modulus. 
							   That is, in the Z_{modulus} multiplicative group.
			degree::int		-- Degree of the new polynomial.
 		"""
        coefficients = []
        random = StrongRandom()
        for i in range(0, degree + 1):
            coeff = random.randint(1, modulus - 1)
            coefficients.append(coeff)

        return cls(modulus, coefficients)
Exemplo n.º 10
0
    def new(cls, public_key, length):
        """
        Generate a new re-encryption information object with the given length.

        This constructs {length} blocks of random ciphertext re-encryption
        information, which can be applied to a given ciphertext in order to
        produce a re-encrypted ciphertext.

        Arguments:
            public_key::PublicKey-- The public key to be used for re-encryption.
                                   This must be the same public key that was
                                   used to encrypt the original ciphertext.
            length::int -- Number of blocks of re-encryption information.

        Returns:
            reencryption_info::CiphertextReencryptionInfo   --
                A new CiphertextReencryptionInfo object containing length
                random blocks or re-encryption information.
        """
        random = StrongRandom()

        # Get p and g
        prime = public_key.cryptosystem.get_prime()
        generator = public_key.cryptosystem.get_generator()

        # Create a new empty CiphertextReencryptionInfo object
        reencryption_info = CiphertextReencryptionInfo(public_key)

        # Add (length) random re-encryption information blocks
        for i in range(0, length):

            # Select a random integer r, 1 <= r <= p − 2
            r = random.randint(1, prime - 2)

            # store block (g^{r}, y^{r})
            gr = pow(generator, r, prime)
            yr = pow(public_key._key, r, prime)
            reencryption_info.add_block(gr, yr)

        assert (reencryption_info.get_length() == length)

        return reencryption_info
Exemplo n.º 11
0
 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
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
 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