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_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
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
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
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
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)
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
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 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 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 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