def test_get_num_beyond_eos(self): """ Test than trying to read beyond the end of the stream raises an exception when calling get_num(...). """ bitstream = BitStream() # Store a 64-bit integer in the stream. num = int('00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) # Check that trying to read 65 bits from the beginning of the stream # raises NotEnoughBitsInStreamError bitstream.seek(0) self.assertRaises(NotEnoughBitsInStreamError, bitstream.get_num, 65) # An invalid read call should not move the position indicator. self.assertEquals(bitstream.get_current_pos(), 0) # Check that trying to read 33 bits from the middle of the stream # (pos = 32) raises NotEnoughBitsInStreamError bitstream.seek(32) self.assertRaises(NotEnoughBitsInStreamError, bitstream.get_num, 33) # An invalid read call should not move the position indicator. self.assertEquals(bitstream.get_current_pos(), 32) # Check that trying to read a single bit while at the end of the stream # raises NotEnoughBitsInStreamError bitstream.seek(64) self.assertRaises(NotEnoughBitsInStreamError, bitstream.get_num, 1) # An invalid read call should not move the position indicator. self.assertEquals(bitstream.get_current_pos(), 64)
def test_stress(self): """ Stress test BitStream by writings lots of numbers of different bit sizes and reading them back. """ # num_writes = 10000 num_writes = 1000 min_bit_length = 1 max_bit_length = 8192 # Generate the numbers nums = [] total_bit_length = 0 for i in range(0, num_writes): bit_length = random.randint(min_bit_length, max_bit_length) total_bit_length += bit_length val = random.randint(0, 2**bit_length - 1) nums.append({"bit_length": bit_length, "value": val}) # Write them to a BitStream object bitstream = BitStream() for num in nums: bitstream.put_num(num["value"], num["bit_length"]) # Check the BitStream length and current position self.assertEquals(bitstream.get_length(), total_bit_length) self.assertEquals(bitstream.get_current_pos(), total_bit_length) # Read all numbers back bitstream.seek(0) for num in nums: self.assertEquals(bitstream.get_num(num["bit_length"]), num["value"])
def test_get_num_beyond_eos(self): """ Test than trying to read beyond the end of the stream raises an exception when calling get_num(...). """ bitstream = BitStream() # Store a 64-bit integer in the stream. num = int( '00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) # Check that trying to read 65 bits from the beginning of the stream # raises NotEnoughBitsInStreamError bitstream.seek(0) self.assertRaises(NotEnoughBitsInStreamError, bitstream.get_num, 65) # An invalid read call should not move the position indicator. self.assertEquals(bitstream.get_current_pos(), 0) # Check that trying to read 33 bits from the middle of the stream # (pos = 32) raises NotEnoughBitsInStreamError bitstream.seek(32) self.assertRaises(NotEnoughBitsInStreamError, bitstream.get_num, 33) # An invalid read call should not move the position indicator. self.assertEquals(bitstream.get_current_pos(), 32) # Check that trying to read a single bit while at the end of the stream # raises NotEnoughBitsInStreamError bitstream.seek(64) self.assertRaises(NotEnoughBitsInStreamError, bitstream.get_num, 1) # An invalid read call should not move the position indicator. self.assertEquals(bitstream.get_current_pos(), 64)
def test_seek_basic(self): """ Use seek to read data at different points in the bitstream. """ bitstream = BitStream() # Store a 64-bit integer in the stream. num = int('00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) # Get the 0th, 3rd, 5th and 6th bytes of the stream # First in forwards order bitstream.seek(0) self.assertEqual(bitstream.get_num(8),int('00000000',2)) bitstream.seek(3*8) self.assertEqual(bitstream.get_num(8),int('01010011',2)) bitstream.seek(5*8) self.assertEqual(bitstream.get_num(8),int('00100010',2)) self.assertEqual(bitstream.get_num(8),int('10001001',2)) # Then in backwards order bitstream.seek(6*8) self.assertEqual(bitstream.get_num(8),int('10001001',2)) bitstream.seek(5*8) self.assertEqual(bitstream.get_num(8),int('00100010',2)) bitstream.seek(3*8) self.assertEqual(bitstream.get_num(8),int('01010011',2)) bitstream.seek(0) self.assertEqual(bitstream.get_num(8),int('00000000',2))
def test_seek_basic(self): """ Use seek to read data at different points in the bitstream. """ bitstream = BitStream() # Store a 64-bit integer in the stream. num = int( '00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) # Get the 0th, 3rd, 5th and 6th bytes of the stream # First in forwards order bitstream.seek(0) self.assertEqual(bitstream.get_num(8), int('00000000', 2)) bitstream.seek(3 * 8) self.assertEqual(bitstream.get_num(8), int('01010011', 2)) bitstream.seek(5 * 8) self.assertEqual(bitstream.get_num(8), int('00100010', 2)) self.assertEqual(bitstream.get_num(8), int('10001001', 2)) # Then in backwards order bitstream.seek(6 * 8) self.assertEqual(bitstream.get_num(8), int('10001001', 2)) bitstream.seek(5 * 8) self.assertEqual(bitstream.get_num(8), int('00100010', 2)) bitstream.seek(3 * 8) self.assertEqual(bitstream.get_num(8), int('01010011', 2)) bitstream.seek(0) self.assertEqual(bitstream.get_num(8), int('00000000', 2))
def test_stress(self): """ Stress test BitStream by writings lots of numbers of different bit sizes and reading them back. """ # num_writes = 10000 num_writes = 1000 min_bit_length = 1 max_bit_length = 8192 # Generate the numbers nums = [] total_bit_length = 0 for i in range(0, num_writes): bit_length = random.randint(min_bit_length, max_bit_length) total_bit_length += bit_length val = random.randint(0, 2**bit_length - 1) nums.append({"bit_length" : bit_length, "value" : val}) # Write them to a BitStream object bitstream = BitStream() for num in nums: bitstream.put_num(num["value"], num["bit_length"]) # Check the BitStream length and current position self.assertEquals(bitstream.get_length(),total_bit_length) self.assertEquals(bitstream.get_current_pos(),total_bit_length) # Read all numbers back bitstream.seek(0) for num in nums: self.assertEquals(bitstream.get_num(num["bit_length"]),num["value"])
def test_multiformat_write_multiformat_read(self): """ This test writes numeric, byte and string data to a stream and then reads the whole stream as binary, hex and base64 data, ensuring that the output is the expected one in each case. """ # Write a number, 2 bytes and a string bitstream = BitStream() bitstream.put_num(10438341575639894917, 64) bitstream.put_byte(230) bitstream.put_byte(191) bitstream.put_string("ÄäÜüßTestЯБГДЖЙŁĄŻStringĘĆŃŚŹてす" \ "とアイウエオカキク4234ケコサシスセソタチツテ") # Read in binary, hex and base64 formats expected_bits = \ "1001000011011100011100000101101110111100001101010000101110000101" \ "1110011010111111110000111000010011000011101001001100001110011100" \ "1100001110111100110000111001111101010100011001010111001101110100" \ "1101000010101111110100001001000111010000100100111101000010010100" \ "1101000010010110110100001001100111000101100000011100010010000100" \ "1100010110111011010100110111010001110010011010010110111001100111" \ "1100010010011000110001001000011011000101100000111100010110011010" \ "1100010110111001111000111000000110100110111000111000000110011001" \ "1110001110000001101010001110111110111101101100011110111110111101" \ "1011001011101111101111011011001111101111101111011011010011101111" \ "1011110110110101111011111011110110110110111011111011110110110111" \ "1110111110111101101110000011010000110010001100110011010011101111" \ "1011110110111001111011111011110110111010111011111011110110111011" \ "1110111110111101101111001110111110111101101111011110111110111101" \ "1011111011101111101111011011111111101111101111101000000011101111" \ "1011111010000001111011111011111010000010111011111011111010000011" expected_hex = \ "90dc705bbc350b85e6bfc384c3a4c39cc3bcc39f54657374d0afd091d093d094" \ "d096d099c581c484c5bb537472696e67c498c486c583c59ac5b9e381a6e38199" \ "e381a8efbdb1efbdb2efbdb3efbdb4efbdb5efbdb6efbdb7efbdb834323334ef" \ "bdb9efbdbaefbdbbefbdbcefbdbdefbdbeefbdbfefbe80efbe81efbe82efbe83" expected_base64 = \ "kNxwW7w1C4Xmv8OEw6TDnMO8w59UZXN00K/QkdCT0JTQltCZxYHEhMW7U3RyaW5n" \ "xJjEhsWDxZrFueOBpuOBmeOBqO+9se+9su+9s++9tO+9te+9tu+9t++9uDQyMzTv" \ "vbnvvbrvvbvvvbzvvb3vvb7vvb/vvoDvvoHvvoLvvoM=" bitstream.seek(0) self.assertEquals( \ bitstream.get_bit_dump_string(bitstream.get_length()), expected_bits) bitstream.seek(0) self.assertEquals( bitstream.get_hex(bitstream.get_length()).lower(), expected_hex) bitstream.seek(0) self.assertEquals(bitstream.get_base64(bitstream.get_length()), expected_base64)
def test_multiformat_write_multiformat_read(self): """ This test writes numeric, byte and string data to a stream and then reads the whole stream as binary, hex and base64 data, ensuring that the output is the expected one in each case. """ # Write a number, 2 bytes and a string bitstream = BitStream() bitstream.put_num(10438341575639894917, 64) bitstream.put_byte(230) bitstream.put_byte(191) bitstream.put_string("ÄäÜüßTestЯБГДЖЙŁĄŻStringĘĆŃŚŹてす" \ "とアイウエオカキク4234ケコサシスセソタチツテ") # Read in binary, hex and base64 formats expected_bits = \ "1001000011011100011100000101101110111100001101010000101110000101" \ "1110011010111111110000111000010011000011101001001100001110011100" \ "1100001110111100110000111001111101010100011001010111001101110100" \ "1101000010101111110100001001000111010000100100111101000010010100" \ "1101000010010110110100001001100111000101100000011100010010000100" \ "1100010110111011010100110111010001110010011010010110111001100111" \ "1100010010011000110001001000011011000101100000111100010110011010" \ "1100010110111001111000111000000110100110111000111000000110011001" \ "1110001110000001101010001110111110111101101100011110111110111101" \ "1011001011101111101111011011001111101111101111011011010011101111" \ "1011110110110101111011111011110110110110111011111011110110110111" \ "1110111110111101101110000011010000110010001100110011010011101111" \ "1011110110111001111011111011110110111010111011111011110110111011" \ "1110111110111101101111001110111110111101101111011110111110111101" \ "1011111011101111101111011011111111101111101111101000000011101111" \ "1011111010000001111011111011111010000010111011111011111010000011" expected_hex = \ "90dc705bbc350b85e6bfc384c3a4c39cc3bcc39f54657374d0afd091d093d094" \ "d096d099c581c484c5bb537472696e67c498c486c583c59ac5b9e381a6e38199" \ "e381a8efbdb1efbdb2efbdb3efbdb4efbdb5efbdb6efbdb7efbdb834323334ef" \ "bdb9efbdbaefbdbbefbdbcefbdbdefbdbeefbdbfefbe80efbe81efbe82efbe83" expected_base64 = \ "kNxwW7w1C4Xmv8OEw6TDnMO8w59UZXN00K/QkdCT0JTQltCZxYHEhMW7U3RyaW5n" \ "xJjEhsWDxZrFueOBpuOBmeOBqO+9se+9su+9s++9tO+9te+9tu+9t++9uDQyMzTv" \ "vbnvvbrvvbvvvbzvvb3vvb7vvb/vvoDvvoHvvoLvvoM=" bitstream.seek(0) self.assertEquals( \ bitstream.get_bit_dump_string(bitstream.get_length()), expected_bits) bitstream.seek(0) self.assertEquals(bitstream.get_hex(bitstream.get_length()).lower(), expected_hex) bitstream.seek(0) self.assertEquals(bitstream.get_base64(bitstream.get_length()), expected_base64)
def test_get_num_zero_bits(self): """ Test that reading zero bits from the stream as a number results in getting the number 0. """ bitstream = BitStream() self.assertEquals(bitstream.get_num(0), 0) # Store a 64-bit integer in the stream. num = int('00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) bitstream.seek(0) self.assertEquals(bitstream.get_num(0), 0)
def test_get_num_zero_bits(self): """ Test that reading zero bits from the stream as a number results in getting the number 0. """ bitstream = BitStream() self.assertEquals(bitstream.get_num(0), 0) # Store a 64-bit integer in the stream. num = int( '00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) bitstream.seek(0) self.assertEquals(bitstream.get_num(0), 0)
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 test_seek_beyond_eos(self): """ Test that seeking beyond the end of the bitstream results in an exception raised. """ bitstream = BitStream() self.assertRaises(SeekOutOfRangeError, bitstream.seek, 1) # empty stream # An invalid seek(...) call should not move the position indicator. self.assertEqual(bitstream.get_current_pos(), 0) # Store a 64-bit integer in the stream. num = int('00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) # Test seeking past 64 bits self.assertRaises(SeekOutOfRangeError, bitstream.seek, 65) self.assertRaises(SeekOutOfRangeError, bitstream.seek, random.randint(66,2**128)) # An invalid seek(...) call should not move the position indicator. self.assertEqual(bitstream.get_current_pos(), 64)
def test_num_basic(self): """ Test basic put_num and get_num behavior """ bitstream = BitStream() # Some parameters num_sizes = [4, 8, 16, 32, 64, 128, 256, 2839] num_writes = 10 nums = [] # Add num_writes integers of each size to nums: for i in range(0, len(num_sizes)): max_val = 2**num_sizes[i] - 1 nums.append([]) for j in range(0, num_writes): # append a random number between 0 and max_val, inclusive nums[i].append(random.randint(0, max_val)) # Write all values in nums to the stream for i in range(0, len(num_sizes)): for j in range(0, num_writes): bitstream.put_num(nums[i][j], num_sizes[i]) # Go back to start of the stream bitstream.seek(0) # Sanity check: expected_length = 0 for num_size in num_sizes: expected_length += num_size * num_writes self.assertEqual(bitstream.get_length(), expected_length) # Read them back and compare for i in range(0, len(num_sizes)): for j in range(0, num_writes): n = bitstream.get_num(num_sizes[i]) self.assertEqual(n, nums[i][j])
def test_num_read_part(self): """ Test that we can read only some bits of a large number and interpret them as a shorter number with the expected value. """ bitstream = BitStream() # Store a 64-bit integer in the stream. num = int( '00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) # Get it as 8-bit numbers bitstream.seek(0) self.assertEqual(bitstream.get_num(8), int('00000000', 2)) self.assertEqual(bitstream.get_num(8), int('00000011', 2)) self.assertEqual(bitstream.get_num(8), int('01010110', 2)) self.assertEqual(bitstream.get_num(8), int('01010011', 2)) self.assertEqual(bitstream.get_num(8), int('00101100', 2)) self.assertEqual(bitstream.get_num(8), int('00100010', 2)) self.assertEqual(bitstream.get_num(8), int('10001001', 2)) self.assertEqual(bitstream.get_num(8), int('10101111', 2)) # Get it as 16-bit numbers bitstream.seek(0) self.assertEqual(bitstream.get_num(16), int('0000000000000011', 2)) self.assertEqual(bitstream.get_num(16), int('0101011001010011', 2)) self.assertEqual(bitstream.get_num(16), int('0010110000100010', 2)) self.assertEqual(bitstream.get_num(16), int('1000100110101111', 2)) # Get it as 32-bit numbers bitstream.seek(0) self.assertEqual(bitstream.get_num(32), int('00000000000000110101011001010011', 2)) self.assertEqual(bitstream.get_num(32), int('00101100001000101000100110101111', 2))
def test_num_basic(self): """ Test basic put_num and get_num behavior """ bitstream = BitStream() # Some parameters num_sizes = [4,8,16,32,64,128,256,2839] num_writes = 10 nums = [] # Add num_writes integers of each size to nums: for i in range(0,len(num_sizes)): max_val = 2**num_sizes[i] - 1 nums.append([]) for j in range(0,num_writes): # append a random number between 0 and max_val, inclusive nums[i].append(random.randint(0, max_val)) # Write all values in nums to the stream for i in range(0,len(num_sizes)): for j in range(0,num_writes): bitstream.put_num(nums[i][j], num_sizes[i]) # Go back to start of the stream bitstream.seek(0) # Sanity check: expected_length = 0 for num_size in num_sizes: expected_length += num_size * num_writes self.assertEqual(bitstream.get_length(), expected_length) # Read them back and compare for i in range(0,len(num_sizes)): for j in range(0,num_writes): n = bitstream.get_num(num_sizes[i]) self.assertEqual(n, nums[i][j])
def test_seek_beyond_eos(self): """ Test that seeking beyond the end of the bitstream results in an exception raised. """ bitstream = BitStream() self.assertRaises(SeekOutOfRangeError, bitstream.seek, 1) # empty stream # An invalid seek(...) call should not move the position indicator. self.assertEqual(bitstream.get_current_pos(), 0) # Store a 64-bit integer in the stream. num = int( '00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) # Test seeking past 64 bits self.assertRaises(SeekOutOfRangeError, bitstream.seek, 65) self.assertRaises(SeekOutOfRangeError, bitstream.seek, random.randint(66, 2**128)) # An invalid seek(...) call should not move the position indicator. self.assertEqual(bitstream.get_current_pos(), 64)
def test_num_read_part(self): """ Test that we can read only some bits of a large number and interpret them as a shorter number with the expected value. """ bitstream = BitStream() # Store a 64-bit integer in the stream. num = int('00000000000000110101011001010011' '00101100001000101000100110101111', 2) bitstream.put_num(num, 64) # Get it as 8-bit numbers bitstream.seek(0) self.assertEqual(bitstream.get_num(8),int('00000000',2)) self.assertEqual(bitstream.get_num(8),int('00000011',2)) self.assertEqual(bitstream.get_num(8),int('01010110',2)) self.assertEqual(bitstream.get_num(8),int('01010011',2)) self.assertEqual(bitstream.get_num(8),int('00101100',2)) self.assertEqual(bitstream.get_num(8),int('00100010',2)) self.assertEqual(bitstream.get_num(8),int('10001001',2)) self.assertEqual(bitstream.get_num(8),int('10101111',2)) # Get it as 16-bit numbers bitstream.seek(0) self.assertEqual(bitstream.get_num(16),int('0000000000000011',2)) self.assertEqual(bitstream.get_num(16),int('0101011001010011',2)) self.assertEqual(bitstream.get_num(16),int('0010110000100010',2)) self.assertEqual(bitstream.get_num(16),int('1000100110101111',2)) # Get it as 32-bit numbers bitstream.seek(0) self.assertEqual(bitstream.get_num(32), int('00000000000000110101011001010011',2)) self.assertEqual(bitstream.get_num(32), int('00101100001000101000100110101111',2))
def test_num_write_part(self): """ Test that we can write a large number piece by piece and read it as whole number. """ bitstream = BitStream() # Store a 64-bit integer in the stream. num = int('00000000000000110101011001010011' '00101100001000101000100110101111', 2) # ... in 8-bit, 16-bit and 32-bit increments bitstream.put_num(int('00000000',2), 8) bitstream.put_num(int('0000001101010110',2), 16) bitstream.put_num(int('01010011001011000010001010001001',2), 32) bitstream.put_num(int('10101111',2), 8) # Get it as a single 64-bit number bitstream.seek(0) self.assertEqual(bitstream.get_num(64),num)
def test_num_write_part(self): """ Test that we can write a large number piece by piece and read it as whole number. """ bitstream = BitStream() # Store a 64-bit integer in the stream. num = int( '00000000000000110101011001010011' '00101100001000101000100110101111', 2) # ... in 8-bit, 16-bit and 32-bit increments bitstream.put_num(int('00000000', 2), 8) bitstream.put_num(int('0000001101010110', 2), 16) bitstream.put_num(int('01010011001011000010001010001001', 2), 32) bitstream.put_num(int('10101111', 2), 8) # Get it as a single 64-bit number bitstream.seek(0) self.assertEqual(bitstream.get_num(64), num)
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_bitstream(self, task_monitor=None): """ Decrypt the ciphertext to a bitstream, using the partial decryptions. At least (threshold) correctly generated partial decryptions for the ciphertext must be registered with this instance in order for decryption to succeed. Arguments: task_monitor::TaskMonitor -- A task monitor for this task. Returns: bitstream::Bitstream -- A bitstream containing the unencrypted data. Throws: InsuficientPartialDecryptionsError -- If there aren't enough partial decryptions registered with this object to perform combined decryption. """ # Get the indexes of all trustees for which we have a registered # partial decryption. We use 1 based indexes here. trustee_indexes = [] for trustee in range(1, self._num_trustees + 1): decryption = self._trustees_partial_decryptions[trustee - 1] if (decryption != None): trustee_indexes.append(trustee) # Check that we have enough trustees. if (len(trustee_indexes) < self._threshold): raise InsuficientPartialDecryptionsError("Not enough partial " \ "decryptions have been registered with this object to " \ "create a combined decryption. Registered partial " \ "decryptions: %d. Required partial decryptions " \ "(threshold): %d." \ % (len(trustee_indexes), self._threshold)) # We only need threshold trustees, exactly. Select those at random. random.shuffle(trustee_indexes) trustee_indexes = trustee_indexes[0:self._threshold] # We get the number of bits and prime for the cryptosystem nbits = self.cryptosystem.get_nbits() prime = self.cryptosystem.get_prime() # prime = 2q + 1 with q prime by construction (see EGCryptoSystem). q = (prime - 1) // 2 # See PublicKey.encrypt_bitstream for why we use nbits - 1 as the block # size. block_size = self.cryptosystem.get_nbits() - 1 # We initialize our bitstream bitstream = BitStream() # We pre-calculate the lagrange coefficients for the trustees in Z_{q} # for x = 0, to avoid doing so for each block of ciphertext. # See below for an explanation of the use of lagrange coefficients. lagrange_coeffs = [None for i in range(0, self._num_trustees + 1)] for trustee in trustee_indexes: lagrange_coeffs[trustee] = \ _lagrange_coefficient(trustee_indexes, trustee, 0, q) # For each block of partial decryption/(gamma, delta) pair of ciphertext for b_index in range(0, self._ciphertext.get_length()): # Each partial decryption block is of the form g^{rP(i)}, where # (gamma, delta) = (g^r, m*g^{r2P(0)}). # # We must first interpolate val=g^{r2P(0)} from the g^{rP(i)}'s. # Interpolation of val (LaTeX): # $g^{r2P(0)}=g^{\sum_{i\in I}r2P(i)\lambda_{i}(0)}= # \prod_{i\in I}\left(g^{rP\left(i\right)}\right)^{2\lambda_{i}(0)}$ # where \lambda_{i}(0) are the Lagrange Coefficients. # # Note that the polynomials are in the field Z_{q} where q is such # that p = 2*q + 1 and prime. This allows us to use lagrange # interpolation. However, in order for the whole g^{...} values to # be equal mod p, we need to have the exponents be equal mod (p-1) # (rather than mod q), so we multiply by 2. Remember that this # means that 2P(0) is our private key for the threshold encryption # (although it never gets created by any of the parties), and the # reason why g^2P(0) is the threshold public key. # # See (TODO: Add reference) for the full explanation gamma, delta = self._ciphertext[b_index] val = 1 for trustee in trustee_indexes: p_decryption = self._trustees_partial_decryptions[trustee - 1] # Get the value (g^{rP(i)}) of the partial decryption block. # Remember that each PartialDecryptionBlock is an object # containing both the value of the block and its proof of # partial decryption pd_block = p_decryption[b_index].value # We get \lambda_{i}(0) in the field Z_{q} for the trustees l_coeff = lagrange_coeffs[trustee] # We assert that the \lambda_{i}(0) was pre-calculated. assert (l_coeff != None), "lagrange coefficients for " \ "trustees in trustee_indexes " \ "should have been pre-calculated." # factor: $\left(g^{rP\left(i\right)}\right)^{2\lambda_{i}(0)}$ factor = pow(pd_block, 2 * l_coeff, prime) val = (val * factor) % prime # We decrypt a block of message as m = delta/val = delta*(val)^{-1}. # (val)^{-1} the inverse of val in Z_{p} inv_val = pow(val, prime - 2, prime) m = (delta * inv_val) % prime # ... and add it to the bitstream. bitstream.put_num(m, block_size) # Return the decrypted bitstream return bitstream
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 generate_commitment(self): """ Generate a ThresholdEncryptionCommitment towards the threshold scheme. Returns: commitment::ThresholdEncryptionCommitment Throws: ThresholdEncryptionSetUpStateError -- If public keys are not loaded. """ # 0. Verify that all public keys are available for 1-to-1 encryption. for trustee in range(0, self._num_trustees): pk = self._trustees_simple_public_keys[trustee] if (pk == None): raise ThresholdEncryptionSetUpStateError( "generate_commitment() must only be called after all the " \ "trustees' public keys have been registered with this " \ "ThresholdEncryptionSetUp instance. Missing public key " \ "for trustee %d." % trustee) # 1. Construct a new random polynomial of degree (threshold - 1) # Note: A polynomial of degree (t - 1) is determined by any t # (distinct) points. degree = self._threshold - 1 nbits = self.cryptosystem.get_nbits() prime = self.cryptosystem.get_prime() generator = self.cryptosystem.get_generator() # All calculations inside the polynomial are performed modulus q # where q is such that p = 2*q + 1 (q is prime because of how we # construct p when generating an EGCryptoSystem). # We do this, because the values of the polynomial coefficients or its # value at a certain point are always used as the exponent of elements # in Z_{p}^{*}, and we know: # a = b mod (p - 1) => g^a = g^b mod p # (because g^{a-b} = g^{x(p-1)} = (g^x)^{p-1} = 1 mod p) # We use q and not p - 1, because Z_{q} is a field (because q is # prime), while the same would not be true for p-1, and we need the # polynomials to be on a field in order to perform Lagrange # Interpolation (see ThresholdDecryptionCombinator.decrypt_to_X()). # This also means that 2*P(0) will be the threshold private key # (never actually seen directly by the parties), and g^(2*P(0)) # the threshold public key. # For the full explanation, see: (TODO: Add reference) q = (prime - 1) / 2 polynomial = \ CoefficientsPolynomial.new_random_polynomial(q, degree) # 2. Generate the public "coefficients" (actually g^coefficient for # each coefficient of the polynomial). public_coeficients = [] for coeff in polynomial.get_coefficients(): public_coeficients.append(pow(generator, coeff, prime)) # 3. Generate the partial private keys for each trustee. # The partial private key for trustee j is P_{i}(j+1), with i the # trustee generating the commitment, its full private key is the sum # of the P_{i}(j+1) values generated by all trustees (including its own). # We must add 1 to the trustee index, since we want the trustee # indexes in the polynomial to start on 1, while our lists are 0 # based. # (We must never reveal P(0) as this is the complete private key # of the full threshold scheme) # IMPORTANT: We encrypt each partial private key so that only its # intended recipient may read it. enc_partial_priv_keys = [] for trustee in range(0, self._num_trustees): ppriv_key = polynomial(trustee + 1) # P_{i}(j) trustee_pk = self._trustees_simple_public_keys[trustee] # Note that trustee public keys need not use the same cryptosystem # as the threshold encryption. In fact, they might not even have # the same bit length. bitstream = BitStream() bitstream.put_num(ppriv_key, nbits) ciphertext = trustee_pk.encrypt_bitstream(bitstream) enc_partial_priv_keys.append(ciphertext) # 4. Construct a ThresholdEncryptionCommitment object storing this # commitment and return it. return ThresholdEncryptionCommitment(self.cryptosystem, self._num_trustees, self._threshold, public_coeficients, enc_partial_priv_keys)
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_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_commitment(self): """ Generate a ThresholdEncryptionCommitment towards the threshold scheme. Returns: commitment::ThresholdEncryptionCommitment Throws: ThresholdEncryptionSetUpStateError -- If public keys are not loaded. """ # 0. Verify that all public keys are available for 1-to-1 encryption. for trustee in range(0, self._num_trustees): pk = self._trustees_simple_public_keys[trustee] if(pk == None): raise ThresholdEncryptionSetUpStateError( "generate_commitment() must only be called after all the " \ "trustees' public keys have been registered with this " \ "ThresholdEncryptionSetUp instance. Missing public key " \ "for trustee %d." % trustee) # 1. Construct a new random polynomial of degree (threshold - 1) # Note: A polynomial of degree (t - 1) is determined by any t # (distinct) points. degree = self._threshold - 1 nbits = self.cryptosystem.get_nbits() prime = self.cryptosystem.get_prime() generator = self.cryptosystem.get_generator() # All calculations inside the polynomial are performed modulus q # where q is such that p = 2*q + 1 (q is prime because of how we # construct p when generating an EGCryptoSystem). # We do this, because the values of the polynomial coefficients or its # value at a certain point are always used as the exponent of elements # in Z_{p}^{*}, and we know: # a = b mod (p - 1) => g^a = g^b mod p # (because g^{a-b} = g^{x(p-1)} = (g^x)^{p-1} = 1 mod p) # We use q and not p - 1, because Z_{q} is a field (because q is # prime), while the same would not be true for p-1, and we need the # polynomials to be on a field in order to perform Lagrange # Interpolation (see ThresholdDecryptionCombinator.decrypt_to_X()). # This also means that 2*P(0) will be the threshold private key # (never actually seen directly by the parties), and g^(2*P(0)) # the threshold public key. # For the full explanation, see: (TODO: Add reference) q = (prime - 1) / 2 polynomial = \ CoefficientsPolynomial.new_random_polynomial(q, degree) # 2. Generate the public "coefficients" (actually g^coefficient for # each coefficient of the polynomial). public_coeficients = [] for coeff in polynomial.get_coefficients(): public_coeficients.append(pow(generator, coeff, prime)) # 3. Generate the partial private keys for each trustee. # The partial private key for trustee j is P_{i}(j+1), with i the # trustee generating the commitment, its full private key is the sum # of the P_{i}(j+1) values generated by all trustees (including its own). # We must add 1 to the trustee index, since we want the trustee # indexes in the polynomial to start on 1, while our lists are 0 # based. # (We must never reveal P(0) as this is the complete private key # of the full threshold scheme) # IMPORTANT: We encrypt each partial private key so that only its # intended recipient may read it. enc_partial_priv_keys = [] for trustee in range(0, self._num_trustees): ppriv_key = polynomial(trustee + 1) # P_{i}(j) trustee_pk = self._trustees_simple_public_keys[trustee] # Note that trustee public keys need not use the same cryptosystem # as the threshold encryption. In fact, they might not even have # the same bit length. bitstream = BitStream() bitstream.put_num(ppriv_key, nbits) ciphertext = trustee_pk.encrypt_bitstream(bitstream) enc_partial_priv_keys.append(ciphertext) # 4. Construct a ThresholdEncryptionCommitment object storing this # commitment and return it. return ThresholdEncryptionCommitment(self.cryptosystem, self._num_trustees, self._threshold, public_coeficients, enc_partial_priv_keys)
def decrypt_to_bitstream(self, task_monitor=None): """ Decrypt the ciphertext to a bitstream, using the partial decryptions. At least (threshold) correctly generated partial decryptions for the ciphertext must be registered with this instance in order for decryption to succeed. Arguments: task_monitor::TaskMonitor -- A task monitor for this task. Returns: bitstream::Bitstream -- A bitstream containing the unencrypted data. Throws: InsuficientPartialDecryptionsError -- If there aren't enough partial decryptions registered with this object to perform combined decryption. """ # Get the indexes of all trustees for which we have a registered # partial decryption. We use 1 based indexes here. trustee_indexes = [] for trustee in range(1, self._num_trustees + 1): decryption = self._trustees_partial_decryptions[trustee - 1] if(decryption != None): trustee_indexes.append(trustee) # Check that we have enough trustees. if (len(trustee_indexes) < self._threshold): raise InsuficientPartialDecryptionsError("Not enough partial " \ "decryptions have been registered with this object to " \ "create a combined decryption. Registered partial " \ "decryptions: %d. Required partial decryptions " \ "(threshold): %d." \ % (len(trustee_indexes), self._threshold)) # We only need threshold trustees, exactly. Select those at random. random.shuffle(trustee_indexes) trustee_indexes = trustee_indexes[0:self._threshold] # We get the number of bits and prime for the cryptosystem nbits = self.cryptosystem.get_nbits() prime = self.cryptosystem.get_prime() # prime = 2q + 1 with q prime by construction (see EGCryptoSystem). q = (prime - 1) / 2 # See PublicKey.encrypt_bitstream for why we use nbits - 1 as the block # size. block_size = self.cryptosystem.get_nbits() - 1 # We initialize our bitstream bitstream = BitStream() # We pre-calculate the lagrange coefficients for the trustees in Z_{q} # for x = 0, to avoid doing so for each block of ciphertext. # See below for an explanation of the use of lagrange coefficients. lagrange_coeffs = [None for i in range(0,self._num_trustees + 1)] for trustee in trustee_indexes: lagrange_coeffs[trustee] = \ _lagrange_coefficient(trustee_indexes, trustee, 0, q) # For each block of partial decryption/(gamma, delta) pair of ciphertext for b_index in range(0, self._ciphertext.get_length()): # Each partial decryption block is of the form g^{rP(i)}, where # (gamma, delta) = (g^r, m*g^{r2P(0)}). # # We must first interpolate val=g^{r2P(0)} from the g^{rP(i)}'s. # Interpolation of val (LaTeX): # $g^{r2P(0)}=g^{\sum_{i\in I}r2P(i)\lambda_{i}(0)}= # \prod_{i\in I}\left(g^{rP\left(i\right)}\right)^{2\lambda_{i}(0)}$ # where \lambda_{i}(0) are the Lagrange Coefficients. # # Note that the polynomials are in the field Z_{q} where q is such # that p = 2*q + 1 and prime. This allows us to use lagrange # interpolation. However, in order for the whole g^{...} values to # be equal mod p, we need to have the exponents be equal mod (p-1) # (rather than mod q), so we multiply by 2. Remember that this # means that 2P(0) is our private key for the threshold encryption # (although it never gets created by any of the parties), and the # reason why g^2P(0) is the threshold public key. # # See (TODO: Add reference) for the full explanation gamma, delta = self._ciphertext[b_index] val = 1 for trustee in trustee_indexes: p_decryption = self._trustees_partial_decryptions[trustee - 1] # Get the value (g^{rP(i)}) of the partial decryption block. # Remember that each PartialDecryptionBlock is an object # containing both the value of the block and its proof of # partial decryption pd_block = p_decryption[b_index].value # We get \lambda_{i}(0) in the field Z_{q} for the trustees l_coeff = lagrange_coeffs[trustee] # We assert that the \lambda_{i}(0) was pre-calculated. assert (l_coeff != None), "lagrange coefficients for " \ "trustees in trustee_indexes " \ "should have been pre-calculated." # factor: $\left(g^{rP\left(i\right)}\right)^{2\lambda_{i}(0)}$ factor = pow(pd_block, 2*l_coeff, prime) val = (val*factor) % prime # We decrypt a block of message as m = delta/val = delta*(val)^{-1}. # (val)^{-1} the inverse of val in Z_{p} inv_val = pow(val, prime - 2, prime) m = (delta*inv_val) % prime # ... and add it to the bitstream. bitstream.put_num(m, block_size) # Return the decrypted bitstream return bitstream