├── README.md ├── commons ├── datasets.py ├── ensemble_aes.py ├── load_datasets.py ├── neural_networks.py └── sca_metrics.py └── run_ensemble.py /README.md: -------------------------------------------------------------------------------- 1 | # Ensembles for Deep Learning-based Profiled Side-Channel Attacks 2 | Repository code to support paper TCHES2020 (issue 4) paper "__Strength in Numbers: Improving Generalization with Ensembles in Machine Learning-based Profiled Side-channel Analysis__" 3 | 4 | Link to the paper: https://tches.iacr.org/index.php/TCHES/article/view/8686 5 | 6 | Authors: Guilherme Perin (Delft University of Technology, The Netherlands), Łukasz Chmielewski (Radboud University Nijmegen and Riscure BV) and Stjepan Picek (Delft University of Technology, The Netherlands) 7 | 8 | ## Datasets ## 9 | The source code is prepared for three datasets: CHES CTF, ASCAD FIXED KEYS, ASCAD RANDOM KEYS. 10 | 11 | ### CHES CTF dataset ### 12 | This dataset contains 45,000 profiling traces, with a fixed key, and additional 5,000 attacking traces with a different and fixed key. Each trace contains 2,200 samples that represent the processing of s-box operations around the first AES encryption round (Original CHES CTF webpage https://chesctf.riscure.com/2018/content?show=training provides only 10k traces for each device. The traces are already normalized with z-score normalization). 13 | 14 | #### Download #### 15 | CHES CTF dataset (ches_ctf.h5 file) can be downloaded from: https://www.dropbox.com/s/lpw1k3so99krmmq/ches_ctf.h5?dl=0 16 | 17 | ### ASCAD FIXED KEY dataset ### 18 | Information about ASCAD FIXED KEY dataset can be found in the original github page: https://github.com/ANSSI-FR/ASCAD/tree/master/ATMEGA_AES_v1/ATM_AES_v1_fixed_key 19 | 20 | 21 | ### ASCAD RANDOM KEY dataset ### 22 | Information about ASCAD RANDOM KEY dataset can be found in the original github page: https://github.com/ANSSI-FR/ASCAD/tree/master/ATMEGA_AES_v1/ATM_AES_v1_variable_key 23 | 24 | ## Code ## 25 | 26 | This section provides explanations on how to run the ensemble SCA code on the above datasets. 27 | 28 | ### Code execution ### 29 | 30 | The ensemble code should be executed from __run_ensemble.py__ file. This file contain the following structure: 31 | 32 | ```python 33 | ensemble_aes = EnsembleAES() 34 | ensemble_aes.set_dataset("ches_ctf") # "ascad_fixed_key", "ascad_random_key" or "ches_ctf" 35 | ensemble_aes.set_leakage_model("HW") 36 | ensemble_aes.set_target_byte(0) 37 | ensemble_aes.set_mini_batch(400) 38 | ensemble_aes.set_epochs(10) 39 | ensemble_aes.run_ensemble( 40 | number_of_models=50, 41 | number_of_best_models=10 42 | ) 43 | ``` 44 | 45 | In the example above, the analysis will generate 50 models and create ensembles from the 50 models and from the 10 best models. After __run_ensemble__ method is finished, the user can plot guessing entropy and success rate for ensembles all models, ensembles best models, best validation model and best attack model. As an example, the following code can be used to generate the plot: 46 | 47 | ```python 48 | import matplotlib.pyplot as plt 49 | plt.subplot(1, 2, 1) 50 | plt.plot(ensemble_aes.get_ge_best_model_validation(), label="GE best validation") 51 | plt.plot(ensemble_aes.get_ge_best_model_attack(), label="GE best attack") 52 | plt.plot(ensemble_aes.get_ge_ensemble(), label="GE Ensemble All Models") 53 | plt.plot(ensemble_aes.get_ge_ensemble_best_models(), label="GE Ensemble Best Models") 54 | plt.xlabel("Traces") 55 | plt.ylabel("Guessing Entropy") 56 | plt.legend() 57 | plt.subplot(1, 2, 2) 58 | plt.plot(ensemble_aes.get_sr_best_model_validation(), label="SR best validation") 59 | plt.plot(ensemble_aes.get_sr_best_model_attack(), label="SR best attack") 60 | plt.plot(ensemble_aes.get_sr_ensemble(), label="SR Ensemble All Models") 61 | plt.plot(ensemble_aes.get_sr_ensemble_best_models(), label="SR Ensemble Best Models") 62 | plt.xlabel("Traces") 63 | plt.ylabel("Success Rate") 64 | plt.legend() 65 | plt.show() 66 | ``` 67 | 68 | ### Neural Networks ### 69 | The provided code generate MLP or CNN with random hyperparameters according to certain user-defined ranges. In __commons/ensemble_aes.py__, the user can find the methods __run_mlp()__ and __run_cnn()__ for random MLPs and random CNNs, respectively. In __commons/neural_networks.py__ we provide the structure for the neural networks. 70 | -------------------------------------------------------------------------------- /commons/datasets.py: -------------------------------------------------------------------------------- 1 | class SCADatasets: 2 | 3 | def __init__(self): 4 | self.trace_set_list = [] 5 | 6 | def get_trace_set(self, trace_set_name): 7 | trace_list = self.get_trace_set_list() 8 | return trace_list[trace_set_name] 9 | 10 | def get_trace_set_list(self): 11 | parameters_ascad_fixed_key = { 12 | "file": "ASCAD.h5", 13 | "key": "4DFBE0F27221FE10A78D4ADC8E490469", 14 | "key_offset": 32, 15 | "input_offset": 0, 16 | "data_length": 32, 17 | "first_sample": 0, 18 | "number_of_samples": 700, 19 | "n_profiling": 50000, 20 | "n_attack": 10000, 21 | "classes": 9, 22 | "good_key": 224, 23 | "number_of_key_hypothesis": 256, 24 | "epochs": 50, 25 | "mini-batch": 50 26 | } 27 | 28 | parameters_ascad_random_key = { 29 | "file": "ascad-variable.h5", 30 | "key": "00112233445566778899AABBCCDDEEFF", 31 | "key_offset": 16, 32 | "input_offset": 0, 33 | "data_length": 50, 34 | "first_sample": 0, 35 | "number_of_samples": 1400, 36 | "n_profiling": 100000, 37 | "n_attack": 1000, 38 | "classes": 9, 39 | "good_key": 34, 40 | "number_of_key_hypothesis": 256, 41 | "epochs": 50, 42 | "mini-batch": 400 43 | } 44 | 45 | parameters_ches_ctf = { 46 | "file": "ches_ctf.h5", 47 | "key": "2EEE5E799D72591C4F4C10D8287F397A", 48 | "key_offset": 32, 49 | "input_offset": 0, 50 | "data_length": 48, 51 | "first_sample": 0, 52 | "number_of_samples": 2200, 53 | "n_profiling": 45000, 54 | "n_attack": 5000, 55 | "classes": 9, 56 | "good_key": 46, 57 | "number_of_key_hypothesis": 256, 58 | "epochs": 50, 59 | "mini-batch": 400 60 | } 61 | 62 | self.trace_set_list = { 63 | "ascad_fixed_key": parameters_ascad_fixed_key, 64 | "ascad_random_key": parameters_ascad_random_key, 65 | "ches_ctf": parameters_ches_ctf 66 | } 67 | 68 | return self.trace_set_list 69 | -------------------------------------------------------------------------------- /commons/ensemble_aes.py: -------------------------------------------------------------------------------- 1 | from tensorflow.keras import backend as backend 2 | from tensorflow.keras.utils import to_categorical 3 | from commons.neural_networks import NeuralNetwork 4 | from commons.sca_metrics import SCAMetrics 5 | from commons.datasets import SCADatasets 6 | from commons.load_datasets import LoadDatasets 7 | import numpy as np 8 | import random 9 | 10 | 11 | class EnsembleAES: 12 | 13 | def __init__(self): 14 | self.number_of_models = 50 15 | self.number_of_best_models = 10 16 | self.ge_all_validation = [] 17 | self.ge_all_attack = [] 18 | self.sr_all_validation = [] 19 | self.sr_all_attack = [] 20 | self.k_ps_all = [] 21 | self.ge_ensemble = None 22 | self.ge_ensemble_best_models = None 23 | self.ge_best_model_validation = None 24 | self.ge_best_model_attack = None 25 | self.sr_ensemble = None 26 | self.sr_ensemble_best_models = None 27 | self.sr_best_model_validation = None 28 | self.sr_best_model_attack = None 29 | self.target_dataset = None 30 | self.l_model = None 31 | self.target_byte = None 32 | self.classes = None 33 | self.epochs = None 34 | self.mini_batch = None 35 | 36 | def set_dataset(self, target): 37 | self.target_dataset = target 38 | 39 | def set_leakage_model(self, leakage_model): 40 | self.l_model = leakage_model 41 | if leakage_model == "HW": 42 | self.classes = 9 43 | else: 44 | self.classes = 256 45 | 46 | def set_target_byte(self, target_byte): 47 | self.target_byte = target_byte 48 | 49 | def set_epochs(self, epochs): 50 | self.epochs = epochs 51 | 52 | def set_mini_batch(self, mini_batch): 53 | self.mini_batch = mini_batch 54 | 55 | def __add_if_one(self, value): 56 | return 1 if value == 1 else 0 57 | 58 | def get_best_models(self, n_models, result_models_validation, n_traces): 59 | result_number_of_traces_val = [] 60 | for model_index in range(n_models): 61 | if result_models_validation[model_index][n_traces - 1] == 1: 62 | for index in range(n_traces - 1, -1, -1): 63 | if result_models_validation[model_index][index] != 1: 64 | result_number_of_traces_val.append( 65 | [result_models_validation[model_index][n_traces - 1], index + 1, 66 | model_index]) 67 | break 68 | else: 69 | result_number_of_traces_val.append( 70 | [result_models_validation[model_index][n_traces - 1], n_traces, 71 | model_index]) 72 | 73 | sorted_models = sorted(result_number_of_traces_val, key=lambda l: l[:]) 74 | 75 | list_of_best_models = [] 76 | for model_index in range(n_models): 77 | list_of_best_models.append(sorted_models[model_index][2]) 78 | 79 | return list_of_best_models 80 | 81 | def run_mlp(self, X_profiling, Y_profiling, X_validation, Y_validation, X_attack, Y_attack, plt_validation, plt_attack, params, 82 | step, fraction): 83 | mini_batch = random.randrange(500, 1000, 100) 84 | learning_rate = random.uniform(0.0001, 0.001) 85 | activation = ['relu', 'tanh', 'elu', 'selu'][random.randint(0, 3)] 86 | layers = random.randrange(2, 8, 1) 87 | neurons = random.randrange(500, 800, 100) 88 | 89 | model = NeuralNetwork().mlp_random(self.classes, params["number_of_samples"], activation, neurons, layers, learning_rate) 90 | model.fit( 91 | x=X_profiling, 92 | y=Y_profiling, 93 | batch_size=self.mini_batch, 94 | verbose=1, 95 | epochs=self.epochs, 96 | shuffle=True, 97 | validation_data=(X_validation, Y_validation), 98 | callbacks=[]) 99 | 100 | ge_validation, sr_validation, kp_krs = SCAMetrics().ge_and_sr(100, model, params, self.l_model, self.target_byte, 101 | X_validation, plt_validation, step, fraction) 102 | ge_attack, sr_attack, _ = SCAMetrics().ge_and_sr(100, model, params, self.l_model, self.target_byte, X_attack, plt_attack, step, 103 | fraction) 104 | 105 | backend.clear_session() 106 | 107 | return ge_validation, ge_attack, sr_validation, sr_attack, kp_krs 108 | 109 | def run_cnn(self, X_profiling, Y_profiling, X_validation, Y_validation, X_attack, Y_attack, plt_validation, plt_attack, params, 110 | step, fraction): 111 | X_profiling = X_profiling.reshape((X_profiling.shape[0], X_profiling.shape[1], 1)) 112 | X_validation = X_validation.reshape((X_validation.shape[0], X_validation.shape[1], 1)) 113 | X_attack = X_attack.reshape((X_attack.shape[0], X_attack.shape[1], 1)) 114 | 115 | mini_batch = random.randrange(500, 1000, 100) 116 | learning_rate = random.uniform(0.0001, 0.001) 117 | activation = ['relu', 'tanh', 'elu', 'selu'][random.randint(0, 3)] 118 | dense_layers = random.randrange(2, 8, 1) 119 | neurons = random.randrange(500, 800, 100) 120 | conv_layers = random.randrange(1, 2, 1) 121 | filters = random.randrange(8, 32, 4) 122 | kernel_size = random.randrange(10, 20, 2) 123 | stride = random.randrange(5, 10, 5) 124 | 125 | model = NeuralNetwork().cnn_random(self.classes, params["number_of_samples"], activation, neurons, conv_layers, filters, 126 | kernel_size, stride, dense_layers, learning_rate) 127 | model.fit( 128 | x=X_profiling, 129 | y=Y_profiling, 130 | batch_size=self.mini_batch, 131 | verbose=1, 132 | epochs=self.epochs, 133 | shuffle=True, 134 | validation_data=(X_validation, Y_validation), 135 | callbacks=[]) 136 | 137 | ge_validation, sr_validation, kp_krs = SCAMetrics().ge_and_sr(100, model, params, self.l_model, self.target_byte, 138 | X_validation, plt_validation, 139 | step, fraction) 140 | ge_attack, sr_attack, _ = SCAMetrics().ge_and_sr(100, model, params, self.l_model, self.target_byte, X_attack, plt_attack, step, 141 | fraction) 142 | 143 | backend.clear_session() 144 | 145 | return ge_validation, ge_attack, sr_validation, sr_attack, kp_krs 146 | 147 | def compute_ensembles(self, kr_nt, correct_key): 148 | 149 | list_of_best_models = self.get_best_models(self.number_of_models, self.ge_all_validation, kr_nt) 150 | 151 | self.ge_best_model_validation = self.ge_all_validation[list_of_best_models[0]] 152 | self.ge_best_model_attack = self.ge_all_attack[list_of_best_models[0]] 153 | self.sr_best_model_validation = self.sr_all_validation[list_of_best_models[0]] 154 | self.sr_best_model_attack = self.sr_all_attack[list_of_best_models[0]] 155 | 156 | kr_ensemble = np.zeros(kr_nt) 157 | krs_ensemble = np.zeros((100, kr_nt)) 158 | kr_ensemble_best_models = np.zeros(kr_nt) 159 | krs_ensemble_best_models = np.zeros((100, kr_nt)) 160 | 161 | for run in range(100): 162 | 163 | key_p_ensemble = np.zeros(256) 164 | key_p_ensemble_best_models = np.zeros(256) 165 | 166 | for index in range(kr_nt): 167 | for model_index in range(self.number_of_models): 168 | key_p_ensemble += np.log(self.k_ps_all[list_of_best_models[model_index]][run][index] + 1e-36) 169 | for model_index in range(self.number_of_best_models): 170 | key_p_ensemble_best_models += np.log(self.k_ps_all[list_of_best_models[model_index]][run][index] + 1e-36) 171 | 172 | key_p_ensemble_sorted = np.argsort(key_p_ensemble)[::-1] 173 | key_p_ensemble_best_models_sorted = np.argsort(key_p_ensemble_best_models)[::-1] 174 | 175 | kr_position = list(key_p_ensemble_sorted).index(correct_key) + 1 176 | kr_ensemble[index] += kr_position 177 | krs_ensemble[run][index] = kr_position 178 | 179 | kr_position = list(key_p_ensemble_best_models_sorted).index(correct_key) + 1 180 | kr_ensemble_best_models[index] += kr_position 181 | krs_ensemble_best_models[run][index] = kr_position 182 | 183 | print("Run {} - GE {} models: {} | GE {} models: {} | ".format(run, self.number_of_models, 184 | int(kr_ensemble[kr_nt - 1] / (run + 1)), 185 | self.number_of_best_models, 186 | int(kr_ensemble_best_models[kr_nt - 1] / (run + 1)))) 187 | 188 | ge_ensemble = kr_ensemble / 100 189 | ge_ensemble_best_models = kr_ensemble_best_models / 100 190 | 191 | sr_ensemble = np.zeros(kr_nt) 192 | sr_ensemble_best_models = np.zeros(kr_nt) 193 | 194 | for index in range(kr_nt): 195 | for run in range(100): 196 | sr_ensemble[index] += self.__add_if_one(krs_ensemble[run][index]) 197 | sr_ensemble_best_models[index] += self.__add_if_one(krs_ensemble_best_models[run][index]) 198 | 199 | return ge_ensemble, ge_ensemble_best_models, sr_ensemble/100, sr_ensemble_best_models/100 200 | 201 | def create_z_score_norm(self, dataset): 202 | z_score_mean = np.mean(dataset, axis=0) 203 | z_score_std = np.std(dataset, axis=0) 204 | return z_score_mean, z_score_std 205 | 206 | def apply_z_score_norm(self, dataset, z_score_mean, z_score_std): 207 | for index in range(len(dataset)): 208 | dataset[index] = (dataset[index] - z_score_mean) / z_score_std 209 | 210 | def run_ensemble(self, number_of_models, number_of_best_models): 211 | 212 | self.number_of_models = number_of_models 213 | self.number_of_best_models = number_of_best_models 214 | 215 | target_params = SCADatasets().get_trace_set(self.target_dataset) 216 | 217 | root_folder = "D:/traces/" 218 | 219 | (X_profiling, Y_profiling), (X_validation, Y_validation), (X_attack, Y_attack), ( 220 | _, plt_validation, plt_attack) = LoadDatasets().load_dataset( 221 | root_folder + target_params["file"], target_params["n_profiling"], target_params["n_attack"], self.target_byte, self.l_model) 222 | 223 | # normalize with z-score 224 | z_score_mean, z_score_std = self.create_z_score_norm(X_profiling) 225 | self.apply_z_score_norm(X_profiling, z_score_mean, z_score_std) 226 | self.apply_z_score_norm(X_validation, z_score_mean, z_score_std) 227 | self.apply_z_score_norm(X_attack, z_score_mean, z_score_std) 228 | 229 | # convert labels to categorical labels 230 | Y_profiling = to_categorical(Y_profiling, num_classes=self.classes) 231 | Y_validation = to_categorical(Y_validation, num_classes=self.classes) 232 | Y_attack = to_categorical(Y_attack, num_classes=self.classes) 233 | 234 | X_profiling = X_profiling.astype('float32') 235 | X_validation = X_validation.astype('float32') 236 | X_attack = X_attack.astype('float32') 237 | 238 | kr_step = 10 # key rank processed for each kr_step traces 239 | kr_fraction = 1 # validation or attack sets are divided by kr_fraction before computing key rank 240 | 241 | self.ge_all_validation = [] 242 | self.sr_all_validation = [] 243 | self.ge_all_attack = [] 244 | self.k_ps_all = [] 245 | 246 | kr_nt = int(len(X_validation) / (kr_step * kr_fraction)) 247 | 248 | # train random MLP 249 | for model_index in range(self.number_of_models): 250 | ge_validation, ge_attack, sr_validation, sr_attack, kp_krs = self.run_mlp(X_profiling, Y_profiling, 251 | X_validation, Y_validation, 252 | X_attack, Y_attack, 253 | plt_validation, plt_attack, 254 | target_params, kr_step, kr_fraction) 255 | self.ge_all_validation.append(ge_validation) 256 | self.ge_all_attack.append(ge_attack) 257 | self.sr_all_validation.append(sr_validation) 258 | self.sr_all_attack.append(sr_attack) 259 | self.k_ps_all.append(kp_krs) 260 | 261 | # train random CNN 262 | # for model_index in range(self.number_of_models): 263 | # ge_validation, ge_attack, sr_validation, sr_attack, kp_krs = self.run_cnn(X_profiling, Y_profiling, 264 | # X_validation, Y_validation, 265 | # X_attack, Y_attack, 266 | # plt_validation, plt_attack, 267 | # target_params, kr_step, kr_fraction) 268 | # self.ge_all_validation.append(ge_validation) 269 | # self.ge_all_attack.append(ge_attack) 270 | # self.sr_all_validation.append(sr_validation) 271 | # self.sr_all_attack.append(sr_attack) 272 | # self.k_ps_all.append(kp_krs) 273 | 274 | ge_ensemble, ge_ensemble_best_models, sr_ensemble, sr_ensemble_best_models = self.compute_ensembles(kr_nt, 275 | target_params["good_key"]) 276 | 277 | self.ge_ensemble = ge_ensemble 278 | self.ge_ensemble_best_models = ge_ensemble_best_models 279 | self.sr_ensemble = sr_ensemble 280 | self.sr_ensemble_best_models = sr_ensemble_best_models 281 | 282 | def get_ge_ensemble(self): 283 | return self.ge_ensemble 284 | 285 | def get_ge_ensemble_best_models(self): 286 | return self.ge_ensemble_best_models 287 | 288 | def get_ge_best_model_validation(self): 289 | return self.ge_best_model_validation 290 | 291 | def get_ge_best_model_attack(self): 292 | return self.ge_best_model_attack 293 | 294 | def get_sr_ensemble(self): 295 | return self.sr_ensemble 296 | 297 | def get_sr_ensemble_best_models(self): 298 | return self.sr_ensemble_best_models 299 | 300 | def get_sr_best_model_validation(self): 301 | return self.sr_best_model_validation 302 | 303 | def get_sr_best_model_attack(self): 304 | return self.sr_best_model_attack 305 | -------------------------------------------------------------------------------- /commons/load_datasets.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import h5py 3 | 4 | 5 | class LoadDatasets: 6 | AES_Sbox = np.array([ 7 | 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 8 | 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 9 | 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 10 | 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 11 | 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 12 | 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 13 | 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 14 | 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 15 | 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 16 | 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 17 | 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 18 | 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 19 | 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 20 | 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 21 | 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 22 | 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 23 | ]) 24 | 25 | def aes_labelize(self, trace_data, byte, leakage_model): 26 | pt_ct = [row[byte] for row in trace_data] 27 | 28 | key_byte = [row[byte + 32] for row in trace_data] 29 | key_byte = np.asarray(key_byte[:]) 30 | 31 | state = [int(x) ^ int(k) for x, k in zip(np.asarray(pt_ct[:]), key_byte)] 32 | 33 | intermediate_values = self.AES_Sbox[state] 34 | 35 | if leakage_model == "HW": 36 | return [bin(iv).count("1") for iv in intermediate_values] 37 | else: 38 | return intermediate_values 39 | 40 | def load_dataset(self, dataset_file, n_profiling, n_attack, target_byte, leakage_model): 41 | 42 | if "ches_ctf.h5" in dataset_file: 43 | in_file = h5py.File(dataset_file, 'r') 44 | profiling_samples = np.array(in_file.get('profiling_traces')) 45 | profiling_data = np.array(in_file.get('profiling_data')) 46 | attack_samples = np.array(in_file.get('attacking_traces')) 47 | attack_data = np.array(in_file.get('attacking_data')) 48 | else: 49 | in_file = h5py.File(dataset_file, "r") 50 | profiling_samples = np.array(in_file['Profiling_traces/traces'], dtype=np.float64) 51 | attack_samples = np.array(in_file['Attack_traces/traces'], dtype=np.float64) 52 | profiling_plaintext = in_file['Profiling_traces/metadata']['plaintext'] 53 | attack_plaintext = in_file['Attack_traces/metadata']['plaintext'] 54 | profiling_key = in_file['Profiling_traces/metadata']['key'] 55 | attack_key = in_file['Attack_traces/metadata']['key'] 56 | profiling_data = np.zeros((n_profiling, 48)) 57 | attack_data = np.zeros((n_profiling, 48)) 58 | for i in range(n_profiling): 59 | profiling_data[i][0:16] = profiling_plaintext[i] 60 | profiling_data[i][32:48] = profiling_key[i] 61 | for i in range(n_attack): 62 | attack_data[i][0:16] = attack_plaintext[i] 63 | attack_data[i][32:48] = attack_key[i] 64 | 65 | nt = n_profiling 66 | na = n_attack 67 | 68 | X_profiling = profiling_samples[0:nt] 69 | Y_profiling = self.aes_labelize(profiling_data[0:nt], target_byte, leakage_model) 70 | X_attack = attack_samples[0:na] 71 | Y_attack = self.aes_labelize(attack_data[0:na], target_byte, leakage_model) 72 | 73 | # attack set is split into validation and attack sets. 74 | X_validation = X_attack[0: int(na / 2)] 75 | Y_validation = Y_attack[0: int(na / 2)] 76 | X_attack = X_attack[int(na / 2): na] 77 | Y_attack = Y_attack[int(na / 2): na] 78 | 79 | profiling_data = profiling_data[0:nt] 80 | validation_data = attack_data[0: int(na / 2)] 81 | attack_data = attack_data[int(na / 2): na] 82 | 83 | return (X_profiling, Y_profiling), (X_validation, Y_validation), (X_attack, Y_attack), ( 84 | profiling_data, validation_data, attack_data) 85 | -------------------------------------------------------------------------------- /commons/neural_networks.py: -------------------------------------------------------------------------------- 1 | from tensorflow.keras.optimizers import Adam, RMSprop, SGD, Adagrad, Adadelta 2 | from tensorflow.keras.layers import Flatten, Dense, Input, Dropout, GaussianNoise 3 | from tensorflow.keras.layers import Conv1D, Conv2D, AveragePooling2D, MaxPooling1D, MaxPooling2D, BatchNormalization, AveragePooling1D 4 | from tensorflow.keras.models import Sequential 5 | 6 | 7 | class NeuralNetwork: 8 | 9 | def __init__(self): 10 | pass 11 | 12 | def mlp_random(self, classes, number_of_samples, activation, neurons, layers, learning_rate): 13 | model = Sequential() 14 | model.add(BatchNormalization(input_shape=(number_of_samples,))) 15 | for l_i in range(layers): 16 | model.add(Dense(neurons, activation=activation, kernel_initializer='he_uniform', bias_initializer='zeros')) 17 | model.add(Dense(classes, activation='softmax')) 18 | model.summary() 19 | optimizer = Adam(lr=learning_rate) 20 | model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy']) 21 | 22 | return model 23 | 24 | def cnn_random(self, classes, number_of_samples, activation, neurons, conv_layers, filters, kernel_size, stride, layers, learning_rate): 25 | model = Sequential() 26 | for layer_index in range(conv_layers): 27 | if layer_index == 0: 28 | model.add(Conv1D(filters=filters, kernel_size=kernel_size, strides=stride, activation='relu', padding='valid', 29 | input_shape=(number_of_samples, 1))) 30 | else: 31 | model.add(Conv1D(filters=filters, kernel_size=kernel_size, strides=stride, activation='relu', padding='valid')) 32 | 33 | model.add(Flatten()) 34 | for layer_index in range(layers): 35 | model.add(Dense(neurons, activation=activation, kernel_initializer='random_uniform', bias_initializer='zeros')) 36 | 37 | model.add(Dense(classes, activation='softmax')) 38 | model.summary() 39 | model.compile(loss='categorical_crossentropy', optimizer=Adam(lr=learning_rate), metrics=['accuracy']) 40 | 41 | return model 42 | -------------------------------------------------------------------------------- /commons/sca_metrics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | from sklearn.utils import shuffle 4 | 5 | 6 | class SCAMetrics: 7 | AES_Sbox = np.array([ 8 | 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 9 | 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 10 | 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 11 | 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 12 | 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 13 | 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 14 | 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 15 | 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 16 | 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 17 | 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 18 | 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 19 | 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 20 | 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 21 | 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 22 | 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 23 | 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 24 | ]) 25 | 26 | def aes_labelize_ge_sr(self, trace_data, byte, key, leakage): 27 | pt_ct = [row[byte] for row in trace_data] 28 | 29 | key_byte = np.full(len(pt_ct), key[byte]) 30 | state = [int(x) ^ int(k) for x, k in zip(np.asarray(pt_ct[:]), key_byte)] 31 | 32 | intermediate_values = self.AES_Sbox[state] 33 | 34 | if leakage == "HW": 35 | return [bin(iv).count("1") for iv in intermediate_values] 36 | else: 37 | return intermediate_values 38 | 39 | def ge_and_sr(self, runs, model, param, leakage_model, byte, x_test, test_trace_data, step, fraction): 40 | nt = len(x_test) 41 | nt_kr = int(nt / fraction) 42 | nt_interval = int(nt / (step * fraction)) 43 | key_ranking_sum = np.zeros(nt_interval) 44 | success_rate_sum = np.zeros(nt_interval) 45 | key_probabilities_key_ranks = np.zeros((runs, nt, 256)) 46 | 47 | # ---------------------------------------------------------------------------------------------------------# 48 | # compute labels for all key hypothesis 49 | # ---------------------------------------------------------------------------------------------------------# 50 | labels_key_hypothesis = np.zeros((256, nt)) 51 | for key_byte_hypothesis in range(0, 256): 52 | key_h = bytearray.fromhex(param["key"]) 53 | key_h[byte] = key_byte_hypothesis 54 | labels_key_hypothesis[key_byte_hypothesis][:] = self.aes_labelize_ge_sr(test_trace_data, byte, key_h, leakage_model) 55 | 56 | # ---------------------------------------------------------------------------------------------------------# 57 | # predict output probabilities for shuffled test or validation set 58 | # ---------------------------------------------------------------------------------------------------------# 59 | output_probabilities = model.predict(x_test) 60 | 61 | probabilities_kg_all_traces = np.zeros((nt, 256)) 62 | for index in range(nt): 63 | probabilities_kg_all_traces[index] = output_probabilities[index][ 64 | np.asarray([int(leakage[index]) for leakage in labels_key_hypothesis[:]]) 65 | ] 66 | 67 | for run in range(runs): 68 | 69 | probabilities_kg_all_traces_shuffled = shuffle(probabilities_kg_all_traces, random_state=random.randint(0, 100000)) 70 | key_probabilities = np.zeros(256) 71 | kr_count = 0 72 | for index in range(nt_kr): 73 | key_probabilities += np.log(probabilities_kg_all_traces_shuffled[index] + 1e-36) 74 | key_probabilities_key_ranks[run][index] = probabilities_kg_all_traces_shuffled[index] 75 | key_probabilities_sorted = np.argsort(key_probabilities)[::-1] 76 | if (index + 1) % step == 0: 77 | key_ranking_good_key = list(key_probabilities_sorted).index(param["good_key"]) + 1 78 | key_ranking_sum[kr_count] += key_ranking_good_key 79 | if key_ranking_good_key == 1: 80 | success_rate_sum[kr_count] += 1 81 | kr_count += 1 82 | print( 83 | "KR: {} | GE for correct key ({}): {})".format(run, param["good_key"], key_ranking_sum[nt_interval - 1] / (run + 1))) 84 | 85 | guessing_entropy = key_ranking_sum / runs 86 | success_rate = success_rate_sum / runs 87 | 88 | return guessing_entropy, success_rate, key_probabilities_key_ranks 89 | -------------------------------------------------------------------------------- /run_ensemble.py: -------------------------------------------------------------------------------- 1 | from commons.ensemble_aes import EnsembleAES 2 | import matplotlib.pyplot as plt 3 | 4 | import tensorflow as tf 5 | 6 | print("Num GPUs Available: ", len(tf.config.experimental.list_physical_devices('GPU'))) 7 | 8 | ensemble_aes = EnsembleAES() 9 | ensemble_aes.set_dataset("ascad_fixed_key") # "ascad_fixed_key", "ascad_random_key" or "ches_ctf" 10 | ensemble_aes.set_leakage_model("HW") 11 | ensemble_aes.set_target_byte(2) 12 | ensemble_aes.set_mini_batch(400) 13 | ensemble_aes.set_epochs(10) 14 | ensemble_aes.run_ensemble( 15 | number_of_models=5, 16 | number_of_best_models=3 17 | ) 18 | 19 | # plotting GE and SR 20 | plt.subplot(1, 2, 1) 21 | plt.plot(ensemble_aes.get_ge_best_model_validation(), label="GE best validation") 22 | plt.plot(ensemble_aes.get_ge_best_model_attack(), label="GE best attack") 23 | plt.plot(ensemble_aes.get_ge_ensemble(), label="GE Ensemble All Models") 24 | plt.plot(ensemble_aes.get_ge_ensemble_best_models(), label="GE Ensemble Best Models") 25 | plt.xlabel("Traces") 26 | plt.ylabel("Guessing Entropy") 27 | plt.legend() 28 | plt.subplot(1, 2, 2) 29 | plt.plot(ensemble_aes.get_sr_best_model_validation(), label="SR best validation") 30 | plt.plot(ensemble_aes.get_sr_best_model_attack(), label="SR best attack") 31 | plt.plot(ensemble_aes.get_sr_ensemble(), label="SR Ensemble All Models") 32 | plt.plot(ensemble_aes.get_sr_ensemble_best_models(), label="SR Ensemble Best Models") 33 | plt.xlabel("Traces") 34 | plt.ylabel("Success Rate") 35 | plt.legend() 36 | plt.show() 37 | --------------------------------------------------------------------------------