예제 #1
0
    def _alternate_hw(plaintext: np.ndarray, key: np.ndarray, subkey: int,
                      hamming_leakage: bool, aes_round: int,
                      aes_operation: int):
        """ The implementation that uses key expansion and intermediate AES keys for different round or operations.

        :param plaintext: the plaintext values.
        :param key: the key the plaintext is to be encrypted with.
        :param subkey: the specific subkey.
        :param aes_round: the AES round to get hamming weights for.
        :param aes_operation: the AES operation to get hamming weights for.
        :param hamming_leakage: whether to use the hamming weight leakage model.
        :returns: the calculated hamming weights.
        """

        length = len(plaintext)
        hamming_weights = np.empty(length, int)
        aes_object = aes.AES(key[0])

        for i in range(length):
            if i <= 1 or not np.array_equal(key[i], key[i - 1]):
                full_key = key[i]
                aes_object.change_key(full_key)

            # single_byte=True asserts that `encrypt` returns an int instead of an ndarray
            hamming_weights[i] = aes_object.encrypt(plaintext[i],
                                                    aes_round,
                                                    aes_operation,
                                                    single_byte=True,
                                                    result_byte=subkey)

        if hamming_leakage:
            return HammingWeight._hamming_leakage(hamming_weights)

        return hamming_weights
 def test_fifth_byte(self):
     """ Test if the fifth byte in the first round gets returned properly"""
     key = np.array([0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c])
     plaintext = np.array([0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07,
                           0x34])
     test_aes_object = aes.AES(key)
     self.assertEqual(aes.sbox[key[5] ^ plaintext[5]], test_aes_object.encrypt(plaintext, round_output=1,
                                                                               result_byte=5, single_byte=True))
    def test_full_aes_encrypt(self):
        """ Test full run of AES encryption"""

        key = np.array([0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c])
        plaintext = np.array([0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07,
                              0x34])
        test_aes_object = aes.AES(key)

        ciphertext = [0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, 0x32]
        self.assertTrue(np.array_equal(test_aes_object.encrypt(plaintext), ciphertext))
    def calculate_leakage_table(self,
                                subkey: int,
                                bar: progressbar.ProgressBar = None,
                                aes_round: int = 1,
                                aes_operation: int = 0,
                                hamming_weight: bool = True):
        """Prepare a multidimensional table containing all the pre-calculated hamming weights for every data point
        at every point in time

        :param subkey: the subkey index to analyze. Must be in the range [0-15]
        :param aes_round: the AES round to attack
        :param bar: The progressbar to update
        :param hamming_weight: whether to use the hamming_weight leakage model
        :param aes_operation: the AES operation to attack, represented as an integer from 0 to 3
        """

        full_key = np.zeros(16, dtype=int)

        self.leakage = np.zeros(shape=(self.KEY_SIZE, self.NUMBER_OF_TRACES),
                                dtype=int)
        aes_object = aes.AES(full_key)
        for key in range(self.leakage.shape[0]):
            for trace in range(self.leakage.shape[1]):
                if aes_round == 1 and aes_operation == 0:
                    box = aes.sbox[key ^ self.plain[trace][subkey]]
                else:
                    if not (trace > 1 and np.array_equal(
                            self.keys[trace], self.keys[trace - 1])):
                        full_key = self.keys[trace]
                        full_key[subkey] = key
                        aes_object.change_key(full_key)
                    box = aes_object.encrypt(self.plain[trace],
                                             round_output=aes_round,
                                             operation=aes_operation,
                                             single_byte=True,
                                             result_byte=subkey)
                if hamming_weight:
                    result = bin(box).split('b')[1].count('1')
                else:
                    result = box
                self.leakage[key, trace] = result
            if bar is not None:
                bar.update(bar.value + 1)
    def poa_output(self,
                   subkey: int,
                   num_traces: int,
                   hamming_weight: bool = False,
                   aes_round: int = 1,
                   aes_operation: int = 0) -> np.ndarray:
        """ Method that returns the output of the AES algorithm we will perform analysis on.

        :param subkey: the subkey index to analyze. Must be in the range [0-15]
        :param num_traces: number of data points to process
        :param hamming_weight: whether to use the hamming_weight leakage model
        :param aes_operation: operation of aes to attack.
        :param aes_round: round of aes to attack.
        :return: output of the aes algorithm
        """

        # Initialize output array, with a row of 1's for noise, which is always present.
        if hamming_weight:
            bit_matrix = np.zeros((num_traces, 2))
        else:
            bit_matrix = np.zeros((num_traces, 9))
        bit_matrix[:, 0] = 1

        aes_object = aes.AES(np.zeros(16, dtype=int))

        # Put our dummy plaintext through the sbox and return the result.
        for i in range(num_traces):
            aes_object.change_key(self.profiling_keys[i])
            temp = aes_object.encrypt(self.profiling_plain[i],
                                      round_output=aes_round,
                                      single_byte=True,
                                      result_byte=subkey,
                                      operation=aes_operation)

            if hamming_weight:
                bit_matrix[i][1] = temp
            else:
                # Use string to calculate which bits are on
                binary_temp = format(temp, '08b')
                for j in range(len(binary_temp)):
                    bit_matrix[i][j + 1] = int(binary_temp[j])
        return bit_matrix
    def key_extraction(self,
                       estimates: np.ndarray,
                       traces: np.ndarray,
                       subkey: int,
                       bar: progressbar.ProgressBar,
                       num_attack_traces: int = 30,
                       top_n_guesses=1,
                       hamming_weight: bool = False,
                       aes_round: int = 1,
                       aes_operation: int = 0) -> int:
        """
        Method for key extraction. Given a set of traces from a system we want to attack,
        we go through the same profiling process as before, then compare the output to our profiles.
        The key that matches the closest is chosen as the best candidate key.

        :param estimates: matrix containing estimations of dependencies
        :param traces: the traces to use
        :param subkey: the subkey index to analyze. Must be in the range [0-15]
        :param bar: the progressbar to update
        :param num_attack_traces: number of data points to attack
        :param top_n_guesses: amount of key guesses to log
        :param hamming_weight: whether to use the hamming_weight leakage model
        :param aes_operation: operation of aes to attack.
        :param aes_round: round of aes to attack.
        :return: the calculated subkey
        """

        min_sums = 255e155
        best_key = -1
        full_key = self.attack_keys[0]
        aes_object = aes.AES(full_key)
        scores = np.zeros(256)
        for key in range(256):
            full_key[subkey] = key
            aes_object.change_key(full_key)
            temp_sum = 0
            for i in range(len(traces[0])):
                for j in range(num_attack_traces):
                    atk_trace = traces[j]
                    temp = aes_object.encrypt(self.attack_plain[j],
                                              round_output=aes_round,
                                              single_byte=True,
                                              result_byte=subkey,
                                              operation=aes_operation)
                    # due to format of 0bXXXXXXXX we split on b and take the second part
                    binary_temp = format(temp, '08b')
                    inner_temp_sum = estimates[i][0]
                    if hamming_weight:
                        inner_temp_sum += estimates[i][1] * temp
                    else:
                        for l in range(len(binary_temp)):
                            inner_temp_sum += int(
                                binary_temp[l]) * estimates[i][l + 1]

                    temp_sum += pow(atk_trace[i] - inner_temp_sum, 2)
                    if bar.max_value is None or bar.value + 1 < bar.max_value:
                        bar.update(bar.value + 1)

            if temp_sum < min_sums:
                min_sums = temp_sum
                best_key = key
            scores[key] = temp_sum

        if self.log_handler is not None:
            self.log_handler.log_to_debug_file(
                'Best 5 key guesses are {}.'.format(np.argsort(scores)[:5]))

        if top_n_guesses == 1:
            return best_key
        return np.argsort(scores)[:top_n_guesses]