├── .gitignore ├── Add_noise.py ├── Attack.py ├── Denoise.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /Add_noise.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | import sys 4 | import h5py 5 | import numpy as np 6 | import random 7 | import matplotlib.pyplot as plt 8 | import shutil 9 | 10 | def load_ascad(ascad_database_file): 11 | check_file_exists(ascad_database_file) 12 | try: 13 | in_file = h5py.File(ascad_database_file, "r") 14 | except: 15 | print("Error: can't open HDF5 file '%s' for reading (it might be malformed) ..." % 16 | ascad_database_file) 17 | sys.exit(-1) 18 | # Load profiling traces 19 | X_profiling = np.array(in_file['Profiling_traces/traces'], dtype=np.int8) 20 | X_profiling = X_profiling.reshape( 21 | (X_profiling.shape[0], X_profiling.shape[1], 1)) 22 | # X_metadata = in_file['Profiling_traces/metadata/'] 23 | # X_desync = X_metadata['desync'] 24 | # print(X_desync) 25 | 26 | # Load attack traces 27 | X_attack = np.array(in_file['Attack_traces/traces'], dtype=np.int8) 28 | X_attack = X_attack.reshape((X_attack.shape[0], X_attack.shape[1], 1)) 29 | X_metadata = in_file['Attack_traces/metadata/'] 30 | return (X_profiling, X_attack) 31 | 32 | def check_file_exists(file_path): 33 | if os.path.exists(file_path) == False: 34 | print("Error: provided file path '%s' does not exist!" % file_path) 35 | sys.exit(-1) 36 | return 37 | 38 | def show(plot): 39 | plt.plot(plot) 40 | plt.show(block=True) 41 | 42 | def addShuffling(traces, shuffling_level): 43 | print('Add shuffling...') 44 | output_traces = np.zeros(np.shape(traces)) 45 | print(np.shape(output_traces)) 46 | for idx in range(len(traces)): 47 | if(idx % 2000 == 0): 48 | print(str(idx) + '/' + str(len(traces))) 49 | trace = traces[idx] 50 | for iter in range(shuffling_level): 51 | rand = np.random.randint(low=0, high=len(traces[0]), size=(2,)) 52 | temp = trace[rand[0]] 53 | trace[rand[0]] = trace[rand[1]] 54 | trace[rand[1]] = temp 55 | output_traces[idx] = trace 56 | return output_traces 57 | 58 | def addClockJitter(traces, clock_range, trace_length): 59 | print('Add clock jitters...') 60 | output_traces = [] 61 | min_trace_length = 100000 62 | for trace_idx in range(len(traces)): 63 | if(trace_idx % 2000 == 0): 64 | print(str(trace_idx) + '/' + str(len(traces))) 65 | trace = traces[trace_idx] 66 | point = 0 67 | new_trace = [] 68 | while point < len(trace)-1: 69 | new_trace.append(int(trace[point])) 70 | # generate a random number 71 | r = random.randint(-clock_range, clock_range) 72 | # if r < 0, delete r point afterward 73 | if r <= 0: 74 | point += abs(r) 75 | # if r > 0, add r point afterward 76 | else: 77 | avg_point = int((trace[point] + trace[point+1])/2) 78 | for _ in range(r): 79 | new_trace.append(avg_point) 80 | point += 1 81 | output_traces.append(new_trace) 82 | return regulateMatrix(output_traces, trace_length) 83 | 84 | def addRandomDelay(traces, a, b, jitter_amplitude, trace_length): 85 | print('Add random delays...') 86 | output_traces = [] 87 | min_trace_length = 100000 88 | for trace_idx in range(len(traces)): 89 | if(trace_idx % 2000 == 0): 90 | print(str(trace_idx) + '/' + str(len(traces))) 91 | trace = traces[trace_idx] 92 | point = 0 93 | new_trace = [] 94 | while point < len(trace)-1: 95 | new_trace.append(int(trace[point])) 96 | # generate a random number 97 | r = random.randint(0, 10) 98 | # 10% probability of adding random delay 99 | if r > 5: 100 | m = random.randint(0, a-b) 101 | num = random.randint(m, m+b) 102 | if num > 0: 103 | for _ in range(num): 104 | new_trace.append(int(trace[point])) 105 | new_trace.append(int(trace[point]+jitter_amplitude)) 106 | new_trace.append(int(trace[point+1])) 107 | point += 1 108 | # if len(new_trace) > trace_length: 109 | # break 110 | output_traces.append(new_trace) 111 | 112 | return regulateMatrix(output_traces, trace_length) 113 | 114 | def addGussianNoise(traces, noise_level): 115 | print('Add Gussian noise...') 116 | if noise_level == 0: 117 | return traces 118 | else: 119 | output_traces = np.zeros(np.shape(traces)) 120 | print(np.shape(output_traces)) 121 | for trace in range(len(traces)): 122 | if(trace % 5000 == 0): 123 | print(str(trace) + '/' + str(len(traces))) 124 | profile_trace = traces[trace] 125 | noise = np.random.normal( 126 | 0, noise_level, size=np.shape(profile_trace)) 127 | output_traces[trace] = profile_trace + noise 128 | return output_traces 129 | 130 | # A function to make sure the noisy traces has same length (padding zeros) 131 | def regulateMatrix(M, size): 132 | maxlen = max(len(r) for r in M) 133 | Z = np.zeros((len(M), maxlen)) 134 | for enu, row in enumerate(M): 135 | if len(row) <= maxlen: 136 | Z[enu, :len(row)] += row 137 | else: 138 | Z[enu, :] += row[:maxlen] 139 | return Z 140 | 141 | 142 | def generate_traces(ascad_database, new_traces_file, train_data, attack_data, 143 | trace_length=700, 144 | noise_level=8, # add Gussian noise 145 | clock_range=4, # add clock jitter 146 | a=5, b=3, delay_amplitude=10, shuffling_level=20): # add random delay 147 | # Open the output labeled file for writing 148 | shutil.copy(ascad_database, new_traces_file) 149 | try: 150 | out_file = h5py.File(new_traces_file, "r+") 151 | except: 152 | print("Error: can't open HDF5 file '%s' for writing ..." % 153 | new_traces_file) 154 | sys.exit(-1) 155 | 156 | all_traces = np.concatenate((train_data, attack_data), axis=0) 157 | 158 | print("Processing traces...") 159 | #profiling_traces = train_data 160 | new_traces = addShuffling(all_traces, shuffling_level) 161 | new_traces = addRandomDelay(new_traces, a, b, delay_amplitude, trace_length) 162 | new_traces = addClockJitter(new_traces, clock_range, trace_length) 163 | new_traces = addGussianNoise(new_traces, noise_level) 164 | print("Store traces...") 165 | del out_file['Profiling_traces/traces'] 166 | out_file.create_dataset('Profiling_traces/traces', data=np.array(new_traces[:50000], dtype=np.int8)) 167 | del out_file['Attack_traces/traces'] 168 | out_file.create_dataset('Attack_traces/traces', data=np.array(new_traces[50000:], dtype=np.int8)) 169 | out_file.close() 170 | print("Done!") 171 | 172 | if __name__ == "__main__": 173 | 174 | clean_trace_dir = "clean.h5" 175 | noisy_traces_dir = "noisy.h5" 176 | 177 | # load traces 178 | print('load traces...') 179 | (x_train_desync0, x_test_desync0) = load_ascad(clean_trace_dir) 180 | print(np.shape(x_train_desync0)) 181 | print(np.shape(x_test_desync0)) 182 | 183 | generate_traces(clean_trace_dir, noisy_traces_dir, 184 | x_train_desync0, x_test_desync0) 185 | -------------------------------------------------------------------------------- /Attack.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | import time 4 | import pickle 5 | import h5py 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | from keras.models import Model, Sequential 9 | from keras.layers import Flatten, Dense, Input, Conv1D, AveragePooling1D, BatchNormalization 10 | from keras.optimizers import * 11 | from keras.callbacks import ModelCheckpoint 12 | from keras.utils import to_categorical 13 | #from clr import OneCycleLR 14 | import random 15 | import matplotlib.pyplot as plot 16 | import matplotlib.ticker as ticker 17 | import os 18 | import numpy as np 19 | import warnings 20 | from keras.models import load_model 21 | 22 | from keras.callbacks import Callback 23 | from keras import backend as K 24 | 25 | # code based on https://github.com/gabzai/Methodology-for-efficient-CNN-architectures-in-SCA/tree/master/ASCAD/N0%3D0 26 | AES_Sbox = np.array([ 27 | 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 28 | 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 29 | 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 30 | 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 31 | 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 32 | 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 33 | 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 34 | 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 35 | 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 36 | 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 37 | 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 38 | 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 39 | 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 40 | 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 41 | 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 42 | 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 43 | ]) 44 | 45 | def show(plot): 46 | plt.plot(plot) 47 | plt.show() 48 | 49 | # Compute the position of the key hypothesis key amongst the hypotheses 50 | def rk_key(rank_array,key): 51 | key_val = rank_array[key] 52 | return np.where(np.sort(rank_array)[::-1] == key_val)[0][0] 53 | 54 | 55 | # Compute the evolution of rank 56 | def rank_compute(prediction, att_plt, key, byte): 57 | """ 58 | - prediction : predictions of the NN 59 | - att_plt : plaintext of the attack traces 60 | - key : Key used during encryption 61 | - byte : byte to attack 62 | """ 63 | 64 | (nb_trs, nb_hyp) = prediction.shape 65 | 66 | idx_min = nb_trs 67 | min_rk = 255 68 | 69 | key_log_prob = np.zeros(nb_hyp) 70 | rank_evol = np.full(nb_trs,255) 71 | prediction = np.log(prediction+1e-40) 72 | 73 | for i in range(nb_trs): 74 | for k in range(nb_hyp): 75 | key_log_prob[k] += prediction[i,AES_Sbox[k^att_plt[i,byte]]] #Computes the hypothesis values 76 | 77 | rank_evol[i] = rk_key(key_log_prob,key) 78 | 79 | return rank_evol 80 | 81 | 82 | # Performs attack 83 | def perform_attacks(nb_traces, predictions, nb_attacks, plt, key, byte=0, shuffle=True): 84 | """ 85 | Performs a given number of attacks to be determined 86 | - nb_traces : number of traces used to perform the attack 87 | - predictions : array containing the values of the prediction 88 | - nb_attacks : number of attack to perform 89 | - plt : the plaintext used to obtain the consumption traces 90 | - key : the key used to obtain the consumption traces 91 | - byte : byte to attack 92 | - shuffle : (boolean, default = True) 93 | """ 94 | 95 | (nb_total, nb_hyp) = predictions.shape 96 | 97 | all_rk_evol = np.zeros((nb_attacks, nb_traces)) 98 | for i in range(nb_attacks): 99 | if shuffle: 100 | l = list(zip(predictions,plt)) 101 | random.shuffle(l) 102 | sp,splt = list(zip(*l)) 103 | sp = np.array(sp) 104 | splt = np.array(splt) 105 | att_pred = sp[:nb_traces] 106 | att_plt = splt[:nb_traces] 107 | 108 | else: 109 | att_pred = predictions[:nb_traces] 110 | att_plt = plt[:nb_traces] 111 | 112 | rank_evolution = rank_compute(att_pred,att_plt,key,byte=byte) 113 | all_rk_evol[i] = rank_evolution 114 | 115 | rk_avg = np.mean(all_rk_evol,axis=0) 116 | return rk_avg 117 | 118 | def check_file_exists(file_path): 119 | if os.path.exists(file_path) == False: 120 | print("Error: provided file path '%s' does not exist!" % file_path) 121 | sys.exit(-1) 122 | return 123 | 124 | def shuffle_data(profiling_x,label_y): 125 | l = list(zip(profiling_x,label_y)) 126 | random.shuffle(l) 127 | shuffled_x,shuffled_y = list(zip(*l)) 128 | shuffled_x = np.array(shuffled_x) 129 | shuffled_y = np.array(shuffled_y) 130 | return (shuffled_x, shuffled_y) 131 | 132 | ### CNN network 133 | def cnn_architecture(input_size=700,learning_rate=0.00001,classes=256): 134 | 135 | # Designing input layer 136 | input_shape = (input_size,1) 137 | img_input = Input(shape=input_shape) 138 | 139 | # 1st convolutional block 140 | x = Conv1D(4, 1, kernel_initializer='he_uniform', activation='selu', padding='same', name='block1_conv1')(img_input) 141 | x = BatchNormalization()(x) 142 | x = AveragePooling1D(2, strides=2, name='block1_pool')(x) 143 | 144 | x = Flatten(name='flatten')(x) 145 | 146 | # Classification layer 147 | x = Dense(10, kernel_initializer='he_uniform', activation='selu', name='fc1')(x) 148 | x = Dense(10, kernel_initializer='he_uniform', activation='selu', name='fc2')(x) 149 | 150 | # Logits layer 151 | x = Dense(classes, activation='softmax', name='predictions')(x) 152 | 153 | # Create model 154 | inputs = img_input 155 | model = Model(inputs, x, name='ascad') 156 | optimizer = Adam(lr=learning_rate) 157 | model.compile(loss='categorical_crossentropy',optimizer=optimizer, metrics=['accuracy']) 158 | model.summary() 159 | return model 160 | 161 | def cnn_architecture_100(input_size=700,learning_rate=0.00001,classes=256): 162 | 163 | # Personal design 164 | input_shape = (input_size,1) 165 | img_input = Input(shape=input_shape) 166 | 167 | # 1st convolutional block 168 | x = Conv1D(32, 1, kernel_initializer='he_uniform', activation='selu', padding='same', name='block1_conv1')(img_input) 169 | x = BatchNormalization()(x) 170 | x = AveragePooling1D(2, strides=2, name='block1_pool')(x) 171 | 172 | # 2nd convolutional block 173 | x = Conv1D(64, 50, kernel_initializer='he_uniform', activation='selu', padding='same', name='block2_conv1')(x) 174 | x = BatchNormalization()(x) 175 | x = AveragePooling1D(50, strides=50, name='block2_pool')(x) 176 | 177 | # 3rd convolutional block 178 | x = Conv1D(128, 3, kernel_initializer='he_uniform', activation='selu', padding='same', name='block3_conv1')(x) 179 | x = BatchNormalization()(x) 180 | x = AveragePooling1D(2, strides=2, name='block3_pool')(x) 181 | 182 | x = Flatten(name='flatten')(x) 183 | 184 | # Classification part 185 | x = Dense(20, kernel_initializer='he_uniform', activation='selu', name='fc1')(x) 186 | x = Dense(20, kernel_initializer='he_uniform', activation='selu', name='fc2')(x) 187 | x = Dense(20, kernel_initializer='he_uniform', activation='selu', name='fc3')(x) 188 | 189 | # Logits layer 190 | x = Dense(classes, activation='softmax', name='predictions')(x) 191 | 192 | # Create model 193 | inputs = img_input 194 | model = Model(inputs, x, name='cnn_best') 195 | optimizer = Adam(lr=learning_rate) 196 | model.compile(loss='categorical_crossentropy',optimizer=optimizer, metrics=['accuracy']) 197 | model.summary() 198 | return model 199 | 200 | def cnn_best(length, lr=0.00001, classes=256): 201 | # From VGG16 design 202 | input_shape = (length, 1) 203 | img_input = Input(shape=input_shape) 204 | # Block 1 205 | x = Conv1D(64, 11, activation='relu', padding='same', name='block1_conv1')(img_input) 206 | x = AveragePooling1D(2, strides=2, name='block1_pool')(x) 207 | # Block 2 208 | x = Conv1D(128, 11, activation='relu', padding='same', name='block2_conv1')(x) 209 | x = AveragePooling1D(2, strides=2, name='block2_pool')(x) 210 | # Block 3 211 | x = Conv1D(256, 11, activation='relu', padding='same', name='block3_conv1')(x) 212 | x = AveragePooling1D(2, strides=2, name='block3_pool')(x) 213 | # Block 4 214 | x = Conv1D(512, 11, activation='relu', padding='same', name='block4_conv1')(x) 215 | x = AveragePooling1D(2, strides=2, name='block4_pool')(x) 216 | # Block 5 217 | x = Conv1D(512, 11, activation='relu', padding='same', name='block5_conv1')(x) 218 | x = AveragePooling1D(2, strides=2, name='block5_pool')(x) 219 | # Classification block 220 | x = Flatten(name='flatten')(x) 221 | x = Dense(4096, activation='relu', name='fc1')(x) 222 | x = Dense(4096, activation='relu', name='fc2')(x) 223 | x = Dense(classes, activation='softmax', name='predictions')(x) 224 | 225 | inputs = img_input 226 | # Create model. 227 | model = Model(inputs, x, name='cnn_best') 228 | #optimizer = Adam(lr=learning_rate) 229 | optimizer = RMSprop(lr=lr) 230 | model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy']) 231 | return model 232 | 233 | def mlp_best(length, lr=0.00001, node=200, layer_nb=6): 234 | model = Sequential() 235 | model.add(Dense(node, input_dim=length, activation='relu')) 236 | for i in range(layer_nb - 2): 237 | model.add(Dense(node, activation='relu')) 238 | model.add(Dense(256, activation='softmax')) 239 | optimizer = RMSprop(lr=lr) 240 | model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy']) 241 | model.summary() 242 | return model 243 | 244 | #### ASCAD helper to load profiling and attack data (traces and labels) (source : https://github.com/ANSSI-FR/ASCAD) 245 | # Loads the profiling and attack datasets from the ASCAD database 246 | def load_ascad(ascad_database_file, load_metadata=False): 247 | check_file_exists(ascad_database_file) 248 | # Open the ASCAD database HDF5 for reading 249 | try: 250 | in_file = h5py.File(ascad_database_file, "r") 251 | except: 252 | print("Error: can't open HDF5 file '%s' for reading (it might be malformed) ..." % ascad_database_file) 253 | sys.exit(-1) 254 | # Load profiling traces 255 | X_profiling = np.array(in_file['Profiling_traces/traces'], dtype=np.float64) 256 | X_profiling = X_profiling.reshape((X_profiling.shape[0], X_profiling.shape[1])) 257 | # Load profiling labels 258 | Y_profiling = np.array(in_file['Profiling_traces/labels']) 259 | # Load attacking traces 260 | X_attack = np.array(in_file['Attack_traces/traces'], dtype=np.float64) 261 | X_attack = X_attack.reshape((X_attack.shape[0], X_attack.shape[1])) 262 | # Load attacking labels 263 | Y_attack = np.array(in_file['Attack_traces/labels']) 264 | if load_metadata == False: 265 | return (X_profiling, Y_profiling), (X_attack, Y_attack) 266 | else: 267 | return (X_profiling, Y_profiling), (X_attack, Y_attack), (in_file['Profiling_traces/metadata']['plaintext'], in_file['Attack_traces/metadata']['plaintext']) 268 | 269 | #### Training model 270 | def train_model(X_profiling, Y_profiling, X_test, Y_test, model, save_file_name, epochs=150, batch_size=100, max_lr=1e-3): 271 | check_file_exists(os.path.dirname(save_file_name)) 272 | # Get the input layer shape 273 | input_layer_shape = model.get_layer(index=0).input_shape 274 | # reshape 2d to 3d if needed (in case attack with CNN) 275 | Reshaped_X_profiling, Reshaped_X_test = X_profiling, X_test 276 | # Save model every epoch 277 | save_model = ModelCheckpoint(save_file_name) 278 | callbacks=[save_model] 279 | 280 | history = model.fit(x=Reshaped_X_profiling, y=to_categorical(Y_profiling, num_classes=256), validation_data=(Reshaped_X_test, to_categorical(Y_test, num_classes=256)), batch_size=batch_size, verbose = 2, epochs=epochs) 281 | return history 282 | 283 | if __name__ == "__main__": 284 | Denoised_data_dir = 'data.h5' 285 | model_name="data.h5" 286 | trained_model = "model.h5" 287 | 288 | # Choose the name of the model 289 | nb_epochs = 1000 290 | batch_size = 256 291 | input_size = 700 292 | learning_rate = 0.0001 293 | nb_traces_attacks = 10000 294 | nb_attacks = 100 295 | 296 | real_key = 224 # key for ascad fixed key 297 | real_key = 34 # key for ascad random key 298 | 299 | start = time.time() 300 | 301 | # Load the profiling traces 302 | (X_profiling, Y_profiling), (X_attack, Y_attack), (plt_profiling, plt_attack) = load_ascad(Denoised_data_dir, load_metadata=True) 303 | 304 | # Shuffle data 305 | #(X_profiling, Y_profiling) = shuffle_data(X_profiling, Y_profiling) 306 | 307 | # Choose your model 308 | model = cnn_best(len(X_profiling[0])) 309 | #model = mlp_best(len(X_profiling[0])) 310 | #model = load_model(trained_model) 311 | 312 | # Record the metrics 313 | history = train_model(X_profiling[10000:45000], Y_profiling[10000:45000], X_profiling[45000:], Y_profiling[45000:], model, ASCAD_trained_models_folder + model_name + ".h5", epochs=nb_epochs, batch_size=batch_size, max_lr=learning_rate) 314 | end=time.time() 315 | print('Temps execution = %d'%(end-start)) 316 | 317 | # Attack on the test traces 318 | predictions = model.predict(X_attack) 319 | avg_rank = np.array(perform_attacks(nb_traces_attacks, predictions, nb_attacks, plt=plt_attack, key=real_key, byte=2), int) 320 | 321 | print("\n t_GE = ") 322 | print(np.where(avg_rank<=0)) 323 | np.save(Denoised_data_dir + '_GE_MLP', avg_rank) 324 | plt.plot(avg_rank) 325 | plt.savefig(Denoised_data_dir + '_GE_MLP.png') 326 | print(model_name) 327 | 328 | -------------------------------------------------------------------------------- /Denoise.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | import sys 4 | import h5py 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | import matplotlib.ticker as ticker 8 | from keras.layers import * 9 | from keras.models import * 10 | from keras import backend as K 11 | from keras.optimizers import * 12 | from keras.callbacks import ModelCheckpoint, Callback 13 | from keras.layers.normalization import BatchNormalization 14 | from keras.utils import multi_gpu_model, to_categorical 15 | from keras.losses import mse 16 | from keras.utils import plot_model 17 | from keras import backend as K 18 | 19 | Sbox = np.array([ 20 | 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 21 | 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 22 | 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 23 | 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 24 | 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 25 | 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 26 | 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 27 | 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 28 | 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 29 | 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 30 | 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 31 | 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 32 | 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 33 | 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 34 | 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 35 | 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 36 | ]) 37 | 38 | ####################### Utility functions ####################### 39 | def check_file_exists(file_path): 40 | if os.path.exists(file_path) == False: 41 | print("Error: provided file path '%s' does not exist!" % file_path) 42 | sys.exit(-1) 43 | return 44 | 45 | def show(plot): 46 | plt.plot(plot) 47 | plt.show() 48 | 49 | def load_ascad(ascad_database_file): 50 | check_file_exists(ascad_database_file) 51 | try: 52 | in_file = h5py.File(ascad_database_file, "r") 53 | except: 54 | print("Error: can't open HDF5 file '%s' for reading (it might be malformed) ..." % ascad_database_file) 55 | sys.exit(-1) 56 | # Load profiling traces 57 | X_profiling = np.array(in_file['Profiling_traces/traces'], int) 58 | X_profiling = X_profiling.reshape((X_profiling.shape[0], X_profiling.shape[1], 1)) 59 | print('Profiling trace shape: ', np.shape(X_profiling)) 60 | # Load attack traces 61 | X_attack = np.array(in_file['Attack_traces/traces'], int) 62 | X_attack = X_attack.reshape((X_attack.shape[0], X_attack.shape[1], 1)) 63 | print('Attack trace shape: ', np.shape(X_attack)) 64 | all_traces = np.concatenate((X_profiling, X_attack), axis = 0) 65 | return all_traces 66 | 67 | def scale(v): 68 | return (v - v.min()) / (v.max() - v.min()) 69 | 70 | def unscale(o, v): 71 | return o * (v.max() - v.min()) + v.min() 72 | 73 | # utility function for SNR calculation 74 | def CalculateSNR(l, IntermediateData): 75 | trace_length = l.shape[1] 76 | mean = np.zeros([256, trace_length]) 77 | var = np.zeros([256, trace_length]) 78 | cpt = np.zeros(256) 79 | i = 0 80 | 81 | for trace in l: 82 | # classify the traces based on its SBox output 83 | # then add the classified traces together 84 | mean[IntermediateData[i]] += trace 85 | var[IntermediateData[i]] += np.square(trace) 86 | # count the trace number for each SBox output 87 | cpt[IntermediateData[i]] += 1 88 | i += 1 89 | 90 | for i in range(256): 91 | # average the traces for each SBox output 92 | mean[i] = mean[i] / cpt[i] 93 | # variance = mean[x^2] - (mean[x])^2 94 | var[i] = var[i] / cpt[i] - np.square(mean[i]) 95 | # Compute mean [var_cond] and var[mean_cond] for the conditional variances and means previously processed 96 | # calculate the trace variation in each points 97 | varMean = np.var(mean, 0) 98 | # evaluate if current point is stable for all S[p^k] cases 99 | MeanVar = np.mean(var, 0) 100 | return (varMean, MeanVar) 101 | 102 | # Calculate SNR 103 | def SNR(trace_directory): 104 | # here, enter your own datapath 105 | data_dir = trace_dir = trace_directory 106 | n_samples = 10000 107 | targeted_sbox_index = 2 108 | targeted_keyExpansion_index = 42 109 | 110 | print("Read Traces, plaintexts, keys, masks values and initialize the labels used afterwards") 111 | f = h5py.File(trace_dir, "r") 112 | trace = h5py.File(trace_dir, "r") 113 | l = trace['Attack_traces/traces'][:n_samples, :] 114 | l = l.astype(float) 115 | 116 | data = np.array(f['Attack_traces/metadata'][:n_samples]) 117 | k = data['key'][:, targeted_sbox_index] 118 | p = data['plaintext'][:, targeted_sbox_index] 119 | r = data['masks'][:, targeted_sbox_index-2] 120 | rout = data['masks'][:, 15] 121 | 122 | print("Calculate intermediate data") 123 | HW = np.array([bin(n).count("1") for n in range(0, 256)]) 124 | SboxOut_withMaskRout = Sbox[k ^ p] ^ rout 125 | hw_SboxOut_withMaskRout = HW[SboxOut_withMaskRout] 126 | SboxOut_withoutMaskRout = SboxOut_withMaskRout ^ rout 127 | hw_SboxOut_withoutMaskRout = HW[SboxOut_withoutMaskRout] 128 | SboxOut_withMaskR = Sbox[k ^ p] ^ r 129 | 130 | plt.set_cmap('Blues') 131 | plt.tight_layout() 132 | 133 | print("Calculate SNR and plot the data") 134 | #IntermediateData = [SboxOut_withMaskRout, SboxOut_withoutMaskRout, SboxOut_withMaskR, rout, r] 135 | #FigureLable = ['SboxOut_withMaskRout', 'SboxOut_withoutMaskRout', 'SboxOut_withMaskR', 'rout', 'r'] 136 | IntermediateData = [SboxOut_withMaskRout] 137 | FigureLable = ['SboxOut_withMaskRout'] 138 | FigureArray = [] 139 | for idx in range(len(IntermediateData)): 140 | varMean, MeanVar = CalculateSNR(l, IntermediateData[idx]) 141 | snr, = plt.plot((varMean / MeanVar), label=FigureLable[idx]) 142 | FigureArray.append(snr) 143 | plt.xlabel('Time Samples') 144 | plt.legend(handles=FigureArray, loc=2) 145 | plt.show() 146 | plt.savefig(trace_directory + '.png') 147 | 148 | def padding(X, Y): 149 | X_trace_length = len(X[0]) 150 | Y_trace_length = len(Y[0]) 151 | diff = int(abs(X_trace_length - Y_trace_length) / 2) 152 | if diff == 0: 153 | print("No difference, returned!") 154 | return X 155 | output = np.zeros((len(Y), X_trace_length, 1)) 156 | if diff != 0: 157 | for i in range(len(Y)): 158 | output[i][:, 0] = np.pad(Y[i][:, 0], (0, diff*2), 'constant') 159 | return output 160 | 161 | def noiseFilter(input, order, model): 162 | filtered_imgs = model.predict(input) 163 | for i in range(order): 164 | filtered_imgs = model.predict(filtered_imgs) 165 | return np.array(filtered_imgs) 166 | 167 | def generate_traces(model, labeled_traces_file, trace_data, trace_data_o): 168 | # Open the output labeled file for writing 169 | try: 170 | out_file = h5py.File(labeled_traces_file, "r+") 171 | except: 172 | print("Error: can't open HDF5 file '%s' for writing ..." % labeled_traces_file) 173 | sys.exit(-1) 174 | 175 | # denoise and unscale the traces 176 | all_traces = unscale(noiseFilter(trace_data, 0, model)[:,:,0], trace_data_o) 177 | 178 | print("Processing profiling traces...") 179 | profiling_traces = all_traces[:50000] 180 | print("Profiling: after") 181 | del out_file['Profiling_traces/traces'] 182 | out_file.create_dataset('Profiling_traces/traces', data=np.array(profiling_traces, dtype=np.int8)) 183 | 184 | print("Processing attack traces...") 185 | attack_traces = all_traces[50000:] 186 | print("Attack: after") 187 | del out_file['Attack_traces/traces'] 188 | out_file.create_dataset('Attack_traces/traces', data=np.array(attack_traces, dtype=np.int8)) 189 | 190 | out_file.close() 191 | 192 | print("File integrity checking...") 193 | out_file = h5py.File(labeled_traces_file, 'r') 194 | profiling = out_file['Profiling_traces/traces'] 195 | print(np.shape(profiling)) 196 | print(np.allclose(profiling[()], profiling_traces)) 197 | attack = out_file['Attack_traces/traces'] 198 | print(np.allclose(attack[()], attack_traces)) 199 | out_file.close() 200 | print("Done!") 201 | 202 | ####################### Denoiser building blocks ####################### 203 | def conv(x, filter_num, window_size, act, max_pool, dp_rate = 0): 204 | y = Conv1D(filter_num, window_size, padding='same')(x) 205 | y = BatchNormalization()(y) 206 | y = Activation(act)(y) 207 | if max_pool > 0: 208 | y = MaxPooling1D(max_pool)(y) 209 | if dp_rate > 0: 210 | y = Dropout(dp_rate)(y) 211 | return y 212 | 213 | def Conv1DTranspose(input_tensor, filters, kernel_size, padding='same'): 214 | x = Lambda(lambda x: K.expand_dims(x, axis=2))(input_tensor) 215 | x = Conv2DTranspose(filters=filters, kernel_size=(kernel_size, 1), padding=padding)(x) 216 | x = Lambda(lambda x: K.squeeze(x, axis=2))(x) 217 | return x 218 | 219 | def deconv(x, filter_num, window_size, act, max_pool, dp_rate = 0): 220 | if max_pool > 0: 221 | y = UpSampling1D(max_pool)(x) 222 | else: 223 | y = x 224 | y = Conv1DTranspose(y, filter_num, window_size) 225 | y = BatchNormalization()(y) 226 | y = Activation(act)(y) 227 | 228 | if dp_rate > 0: 229 | y = Dropout(dp_rate)(y) 230 | return y 231 | 232 | def DAE(lr, input_length): 233 | img_input = Input(shape=(input_length, 1)) 234 | #encoder 235 | x = conv(img_input, 256, 2, 'selu', 0) 236 | x = conv(x, 256, 2, 'selu', 0) 237 | x = conv(x, 256, 2, 'selu', 0) 238 | x = conv(x, 256, 2, 'selu', 0) 239 | x = conv(x, 256, 2, 'selu', 0) 240 | x = conv(x, 256, 2, 'selu', 5) 241 | x = conv(x, 128, 2, 'selu', 0) 242 | x = conv(x, 128, 2, 'selu', 0) 243 | x = conv(x, 128, 2, 'selu', 0) 244 | x = conv(x, 128, 2, 'selu', 2) 245 | x = conv(x, 64, 2, 'selu', 0) 246 | x = conv(x, 64, 2, 'selu', 0) 247 | x = conv(x, 64, 2, 'selu', 0) 248 | x = conv(x, 64, 2, 'selu', 2) 249 | x = Flatten(name='flatten')(x) 250 | 251 | x = Dense(512, activation='selu')(x) 252 | 253 | x = Dense(2240, activation='selu')(x) 254 | x = Reshape((35, 64))(x) 255 | x = deconv(x, 64, 2, 'selu', 2) 256 | x = deconv(x, 64, 2, 'selu', 0) 257 | x = deconv(x, 64, 2, 'selu', 0) 258 | x = deconv(x, 64, 2, 'selu', 0) 259 | x = deconv(x, 128, 2, 'selu', 2) 260 | x = deconv(x, 128, 2, 'selu', 0) 261 | x = deconv(x, 128, 2, 'selu', 0) 262 | x = deconv(x, 128, 2, 'selu', 0) 263 | x = deconv(x, 256, 2, 'selu', 5) 264 | x = deconv(x, 256, 2, 'selu', 0) 265 | x = deconv(x, 256, 2, 'selu', 0) 266 | x = deconv(x, 256, 2, 'selu', 0) 267 | x = deconv(x, 256, 2, 'selu', 0) 268 | x = deconv(x, 256, 2, 'selu', 0) 269 | 270 | x = deconv(x, 1, 2, 'sigmoid', 0) 271 | 272 | model = Model(img_input, x) 273 | #model = multi_gpu_model(model) 274 | opt = RMSprop(lr=lr) 275 | model.compile(loss='mse', optimizer='adam', metrics=['accuracy']) 276 | model.summary() 277 | return model 278 | 279 | 280 | 281 | if __name__ == "__main__": 282 | noisy_traces_dir = "Noisy.h5" 283 | clean_trace_dir = "Clean.h5" 284 | denoised_traces_dir = "Denoised.h5" 285 | model_dir = "model.h5" 286 | 287 | # load traces 288 | print('load traces...') 289 | X_noisy_o = load_ascad(noisy_traces_dir) 290 | Y_clean_o = load_ascad(clean_trace_dir) 291 | X_noisy = scale(X_noisy_o) 292 | Y_clean = scale(Y_clean_o) 293 | 294 | print('train model...') 295 | autoencoder = DAE(0.0001, len(X_noisy[0])) 296 | #autoencoder = load_model(model_dir) 297 | 298 | # save_model = ModelCheckpoint(model_dir) 299 | # callbacks = [save_model] 300 | 301 | autoencoder.fit(X_noisy[:10000], Y_clean[:10000], 302 | validation_data=(X_noisy[45000:], Y_clean[45000:]), 303 | epochs=100, 304 | batch_size=128, 305 | verbose=2) 306 | 307 | print('generate traces...') 308 | generate_traces(autoencoder, denoised_traces_dir, X_noisy, Y_clean_o) 309 | SNR(denoised_traces_dir) 310 | 311 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 AISyLab @ TU Delft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # On Pre-processing of Side-channel Measurements with Autoencoders 2 | Repository code to support paper TCHES2020 (issue 4) paper "__Remove Some Noise: On Pre-processing of Side-channel Measurements with Autoencoders__" 3 | 4 | Link to the paper: https://tches.iacr.org/index.php/TCHES/article/view/8688 5 | 6 | Authors: Lichao Wu and Stjepan Picek 7 | 8 | To preprodeuce the results in the paper, run add_noise.py -> denoiser.py -> attack.py 9 | --------------------------------------------------------------------------------