def list_of_best_indices(traces: np.ndarray, keys: np.ndarray, plain: np.ndarray, points_of_interest: int, subkey: int, aes_round: int = 1, aes_operation: int = 0, hamming_weight: bool = True, normalization: bool = True): """ This method performs the full SOST or SOSD analysis. :param traces: the traces used for the tool :param keys: the keys used for the tool :param plain: the plain used for the tool :param points_of_interest: the number of points of interest to extract. :param subkey: the subkey to correlate on. Used for hamming weight calculation, must be in the range [0-15]. :param aes_round: the AES round to 'attack'. :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. :param normalization: whether to normalize the coefficients or not. Normalization is part of SOST, without it, SOSD is run. :return List of best n indices """ hamming_weights = HammingWeight.hamming_weights( plain, keys, subkey, aes_round, aes_operation, hamming_weight) indices = SOSTD.list_of_indices_per_value(hamming_weights, traces, hamming_weight) sostd_coefficients = SOSTD.sostd_coefficients(indices, traces, hamming_weight, normalization) return np.argsort(-sostd_coefficients)[:points_of_interest][::-1]
def solve_subkey(self, subkey: int, debug_mode_enabled: bool = False, round_: int = 1, operation: int = 0, bar: progressbar.ProgressBar = None): """ Solve a subkey :param subkey: the subkey index to analyze. Must be in the range [0-15] :param debug_mode_enabled: whether to enable debug mode :param round_: round of aes. :param operation: operation of aes. :param bar: the progressbar to update :return: the calculated subkey corresponding to the subkey index specified """ log_handler = LogHandler("mia", debug_mode_enabled) log_handler.log_to_debug_file('RUNNING dpa attack with \n' 'NUM_FEATURES: {} \n' 'ATTACK_TRACES: {} \n' 'SUBKEY: {}.'.format( self.RANGE, self.NUMBER_OF_TRACES, subkey)) differential = np.zeros(self.KEY_SIZE) means = np.zeros((self.RANGE, 2)) for key in range(self.KEY_SIZE): self.keys[:, subkey] = key leakage_table = HammingWeight.bitwise_hamming( self.plain, self.keys, subkey, round_, operation, self.bit) list1 = [] list0 = [] for i in range(self.NUMBER_OF_TRACES): if leakage_table[i] == 1: list1.append(i) else: list0.append(i) if self.OFFSET == 0: for i in range(self.RANGE): means[i][0] = np.mean(self.traces[list0, i]) means[i][1] = np.mean(self.traces[list1, i]) else: for i in range(self.RANGE - self.OFFSET): means[i][0] = np.mean( np.abs(self.traces[list0, i] - self.traces[list0, i + self.OFFSET])) means[i][1] = np.mean( np.abs(self.traces[list1, i] - self.traces[list1, i + self.OFFSET])) temp = np.zeros(self.RANGE - self.OFFSET) for i in range(self.RANGE - self.OFFSET): temp[i] = np.abs(means[i][1] - means[i][0]) differential[key] = np.max(temp) if bar is not None and bar.value < bar.max_value: bar.update(bar.value + 1) return np.argmax(differential)
def calculate_mean_x_given_y_matrix(plain_texts: np.ndarray, traces: np.ndarray, sub_byte_index_to_analyze: int, keys: np.ndarray, hamming_weight: bool = True, aes_round: int = 1, aes_operation: int = 0) -> np.ndarray: """Mean_y_given_x means that we calculate for every possible sub byte x and a given timestamp the mean of y at that point x specifies a sub_byte in the plain_text, y specifies an output trace :param plain_texts: the plaintexts to use :param traces: the traces to use :param keys: the keys to use :param sub_byte_index_to_analyze: the subkey index to analyze. Must be in the range [0-15] :param hamming_weight: whether to use the hamming_weight leakage model :param aes_round; the round of aes to analyze :param aes_operation: the operation of aes to analyze :return: a numpy matrix containing means for every sub byte """ length_trace = len(traces[0]) hamming_weights = HammingWeight.hamming_weights( plain_texts, keys, sub_byte_index_to_analyze, aes_round, aes_operation, hamming_weight) if hamming_weight: amount_of_values = 9 else: amount_of_values = 256 mean_y_given_x_matrix = np.empty((amount_of_values, length_trace)) for i in range(amount_of_values): traces_with_x = [] # Filter out the traces which contain the current sub byte for j in range(len(plain_texts)): if hamming_weights[j] == i: traces_with_x.append(j) # Now calculate the mean of this specific x for every given timestamp for j in range(length_trace): list_values = np.empty((len(traces_with_x))) for k in range(len(traces_with_x)): list_values[k] = traces[traces_with_x[k]][j] if len(list_values) > 0: mean_y_given_x_matrix[i][j] = np.mean(list_values) # If there are no traces which contain the following x set the mean at 0, this will not happen # if the trace size is much bigger than 255 # TODO: maybe find a more sophisticated solution to this problem else: mean_y_given_x_matrix[i][j] = 0 return mean_y_given_x_matrix
def solve_subkey(self, subkey: int, hamming_leakage: bool = True, debug_mode_enabled: bool = False, round_: int = 1, operation: int = 0, bar: progressbar.ProgressBar = None, conditional_averaging: bool = False): """ Solve a subkey :param subkey: the subkey index to analyze. Must be in the range [0-15] :param hamming_leakage: whether to use the hamming_weight leakage model :param debug_mode_enabled: whether to enable debug mode :param round_: round of aes. :param operation: operation of aes. :param bar: the progressbar to update :param conditional_averaging: whether to use conditional averaging or not. :return: the calculated subkey corresponding to the subkey index specified """ num_values = 256 if hamming_leakage: num_values = 9 if conditional_averaging: return CPA.conditional_avg(self, subkey, hamming_leakage, round_, operation, bar) log_handler = LogHandler("cpa", debug_mode_enabled) log_handler.log_to_debug_file('RUNNING cpa attack with \n' 'NUM_FEATURES: {} \n' 'ATTACK_TRACES: {} \n' 'SUBKEY: {}.'.format( self.RANGE, self.NUMBER_OF_TRACES, subkey)) correlation = np.zeros(self.KEY_SIZE) for key in range(self.KEY_SIZE): for i in range(len(self.traces)): self.keys[i][subkey] = key leakage_table = HammingWeight.hamming_weights( self.plain, self.keys, subkey, round_, operation, hamming_leakage) / num_values - 1 if self.online: correlation[key] = max(self.online_coeffs(leakage_table)) else: correlation[key] = max( Pearson.pearson_cpa(leakage_table, self.traces)) if bar is not None and bar.value < bar.max_value: bar.update(bar.value + 1) return np.argmax(correlation)
def test_calculate_hamming_weights(self): """Test that the calculated hamming weights are correct.""" plaintext = np.array([ [ 253, 175, 146, 139, 39, 24, 40, 196, 38, 225, 26, 31, 238, 15, 0, 30 ], [ 162, 7, 17, 31, 86, 200, 98, 60, 166, 235, 184, 253, 211, 48, 91, 149 ], [ 16, 181, 1, 53, 76, 95, 58, 44, 17, 219, 19, 230, 141, 133, 68, 136 ], [ 48, 0, 86, 35, 156, 15, 97, 135, 201, 46, 209, 49, 238, 12, 197, 127 ], [ 14, 44, 55, 24, 226, 225, 128, 116, 29, 129, 98, 180, 185, 188, 241, 254 ], ]) key = np.array([ [ 34, 123, 137, 185, 176, 241, 60, 235, 195, 90, 64, 204, 173, 62, 49, 13 ], [ 5, 203, 149, 118, 67, 107, 181, 88, 186, 63, 9, 246, 52, 7, 144, 59 ], [ 243, 98, 136, 17, 128, 170, 12, 18, 143, 62, 83, 49, 244, 204, 247, 192 ], [ 152, 77, 141, 53, 182, 175, 145, 242, 232, 203, 104, 91, 5, 174, 206, 126 ], [ 0, 236, 171, 201, 204, 216, 94, 50, 233, 124, 230, 23, 13, 100, 65, 150 ], ]) hamming_weights = HammingWeight.hamming_weights(plaintext, key, 0) self.assertTrue(np.array_equal(hamming_weights, [5, 4, 2, 3, 5]))
def calc_traces_hamming_weight( self, subkey: int, leakage_model: bool, min_points_of_interest: int = 2) -> List[np.ndarray]: """ Method that calculates intermediate sbox and hamming weight traces. :param subkey: the subkey index to analyze. Must be in the range [0-15] :param leakage_model: the leakage model to use :param min_points_of_interest: minimum amount of points required per class. :return: list containing matrix per hamming weight """ # Create the right hamming weight structure. template_hamming_weight = HammingWeight.hamming_weights( self.template_plain, self.template_keys, subkey, hamming_leakage=leakage_model) if leakage_model: hamming_weight_size = 9 else: hamming_weight_size = 256 # Put each trace in the right category and change into NumPy array. template_traces_hamming_weight = [[] for _ in range(hamming_weight_size)] for i in range(len(self.template_traces)): temp = template_hamming_weight[i] template_traces_hamming_weight[temp].append( self.template_traces[i]) template_traces_hamming_weight = [ np.array(template_traces_hamming_weight[i]) for i in range(hamming_weight_size) ] min_length = min_points_of_interest for i in range(len(template_traces_hamming_weight)): if len(template_traces_hamming_weight[i]) < min_length: min_length = len(template_traces_hamming_weight[i]) if min_length < min_points_of_interest: print( 'ERROR: Not enough values of certain class. Maximum amount of points of interest for this ' 'dataset is %d' % min_length) exit(1) return template_traces_hamming_weight
def run(traces: np.ndarray, keys: np.ndarray, plain: np.ndarray, points_of_interest: int, save_traces: bool, subkey: int, aes_round: int = 1, aes_operation: int = 0, leakage_model: bool = True): """ This method performs the full Pearson correlation analysis. :param traces: the traces used for the tool :param keys: the keys used for the tool :param plain: the plain used for the tool :param points_of_interest: the number of points of interest to extract. :param save_traces: whether to save the full traces or just their indices. :param subkey: the subkey to correlate on. Used for hamming weight calculation, must be in the range [0-15]. :param aes_round: the AES round to 'attack'. :param aes_operation: the AES operation to 'attack', represented as an integer from 0 to 3. :param leakage_model: the leakage model to use. """ assert 0 <= subkey < 16, "Subkey must be in the range [0-15]" hamming_weights = HammingWeight.hamming_weights( plain, keys, subkey, aes_round, aes_operation, leakage_model) indices = Pearson.pearson_coefficients(hamming_weights, points_of_interest, traces) if save_traces: print('Saving interesting traces... ', end='') np.save('out/pearson_correlation_selected_traces', traces[:, indices]) else: print('Saving indices of interesting traces... ', end='') np.save('out/pearson_correlation_selected_indices', indices) print('Done')
def best_indices(traces: np.ndarray, keys: np.ndarray, plain: np.ndarray, points_of_interest: int, subkey: int, aes_round: int = 1, aes_operation: int = 0, leakage_model: bool = True): """ This method performs the full Pearson correlation analysis. :param traces: the traces used for the tool :param keys: the keys used for the tool :param plain: the plain used for the tool :param points_of_interest: the number of points of interest to extract. :param subkey: the subkey to correlate on. Used for hamming weight calculation, must be in the range [0-15]. :param aes_round: the AES round to 'attack'. :param aes_operation: the AES operation to 'attack', represented as an integer from 0 to 3. :param leakage_model: whether to use hamming weight leakage model """ hamming_weights = HammingWeight.hamming_weights( plain, keys, subkey, aes_round, aes_operation, leakage_model) if leakage_model: counts = np.zeros(9) else: counts = np.zeros(256) for i in hamming_weights: counts[i] += 1 for i in counts: if i < 2: print( 'WARNING: Pearson does not have enough data points of certain value, ' 'results may not be accurate.') return Pearson.pearson_coefficients(hamming_weights, points_of_interest, traces) return Pearson.pearson_coefficients(hamming_weights, points_of_interest, traces)
def calculate_function_matrix(self, subkey: int): """ Applies model functions to the traces and plains per possible subkey hypothesis byte current 10 model functions: functions m0 till m7: returns the i'th bith of the input hamming_weight m8: returns the hamming weights for every subkey hypothesis basis function m9: just returns 1 for every leakage :param: the subkey index to calculate :return: matrix containing model functions applied to each possible subkey and all traces """ functions_per_byte_value_matrix = np.zeros( (self.KEY_SIZE, self.number_of_traces, 10)) for subkey_hypothesis in range(self.KEY_SIZE): plain = self.plains keys = np.array([[subkey_hypothesis] * 16] * self.number_of_traces) hamming_weight_func = HammingWeight.hamming_weights( plain, keys, subkey) matrix_m = np.ones((10, self.number_of_traces)) matrix_m[9] = hamming_weight_func for i in range(8): for j in range(self.number_of_traces): xor = plain[j][subkey] ^ subkey_hypothesis matrix_m[i][j] = xor >> i & 1 matrix_m = np.swapaxes(matrix_m, 0, 1) functions_per_byte_value_matrix[subkey_hypothesis] = matrix_m self.bar.update(self.bar.value + 1) return functions_per_byte_value_matrix
def test_leakage_func(self): result = HammingWeight._hamming_leakage( np.array([0xFF, 0x3F, 0x2C, 0xD9])) print(result) self.assertTrue(np.array_equal(result, [8, 6, 3, 5]))
def conditional_avg(self, subkey: int, hamming_leakage: bool, round_: int = 1, operation: int = 0, bar: progressbar.ProgressBar = None): """ Calculates coefficients using conditional averaging. :param subkey: the subkey to calculate. :param hamming_leakage: signifies which leakage model is used. :param round_: round of aes. :param operation: operation of aes. :param bar: the progressbar to update. :return: the coefficients, calculated using conditional averaging. """ num_traces = len(self.traces) num_values = 256 if hamming_leakage: num_values = 9 results = np.zeros(self.KEY_SIZE) for key in range(self.KEY_SIZE): conditional_avg = np.zeros(num_values) count = np.zeros(num_values) for i in range(len(self.traces)): self.keys[i][subkey] = key leakage_table = HammingWeight.hamming_weights( self.plain, self.keys, subkey, round_, operation, hamming_leakage) var_range = np.var(leakage_table) mean_range = np.mean(leakage_table) covariance_index = np.zeros(len(self.traces[0])) for x in range(len(self.traces[0])): for i in range(num_traces): conditional_avg[leakage_table[i]] = conditional_avg[ leakage_table[i]] + self.traces[i][x] count[leakage_table[i]] = count[leakage_table[i]] + 1 for i in range(np.power(2, num_traces)): conditional_avg[i] = conditional_avg[i] / count[i] for x in range(len(self.traces[0])): mean = np.mean(conditional_avg) variance = np.var(conditional_avg) covariance = 0 for i in range(num_values): covariance = covariance + i * conditional_avg[i] covariance_index[x] = np.abs(covariance / num_values - mean * mean_range) / np.sqrt( variance * var_range) if bar is not None and bar.value < bar.max_value: bar.update(bar.value + 1) results[key] = np.max(covariance_index) return np.argmax(results)