├── .gitignore ├── README.md ├── requirements.txt ├── pca.py ├── svm.py ├── rf.py ├── cpa.py ├── utils.py ├── ta.py ├── fisher.py └── file_operate.py /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /data/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The python demo for template side channel attacks with power traces. 2 | 3 | You should install the required packages with requirements.txt first. 4 | 5 | The data, which should be placed under the ./data folder, can be downloaded 6 | from [HERE](https://drive.google.com/file/d/13Jp_opqVEXtsH8Els0Ucy7TXv0JzCy23/view?usp=sharing) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2020.12.5 2 | cycler==0.10.0 3 | decorator==4.4.2 4 | dtwalign==0.1.0 5 | joblib==1.0.0 6 | kiwisolver==1.3.0 7 | llvmlite==0.34.0 8 | matplotlib==3.3.2 9 | mkl-fft==1.2.0 10 | mkl-random==1.1.1 11 | mkl-service==2.3.0 12 | networkx==2.5 13 | numba==0.51.2 14 | numpy==1.19.2 15 | olefile==0.46 16 | pandas==1.1.3 17 | patsy==0.5.1 18 | Pillow==8.0.1 19 | pip==20.2.4 20 | pyparsing==2.4.7 21 | python-dateutil==2.8.1 22 | pytz==2020.1 23 | scikit-learn==0.23.2 24 | scipy==1.5.2 25 | seaborn==0.11.0 26 | setuptools==50.3.0.post20201103 27 | sip==4.19.13 28 | six==1.15.0 29 | statsmodels==0.12.0 30 | threadpoolctl==2.1.0 31 | tornado==6.0.4 32 | wheel==0.35.1 33 | wincertstore==0.2 34 | -------------------------------------------------------------------------------- /pca.py: -------------------------------------------------------------------------------- 1 | from utils import * 2 | 3 | 4 | class PCA: 5 | ''' 6 | A general PCA class 7 | ''' 8 | proj_matrix = None 9 | 10 | def __init__(self, X, explain_ratio=0.95): 11 | cov_matrix = np.cov(X.T) 12 | eigen_values, eigen_vectors = np.linalg.eig(cov_matrix) 13 | esum = np.sum(eigen_values) 14 | variance_explained = np.zeros(eigen_values.shape[0]) 15 | for i, v in enumerate(eigen_values): 16 | variance_explained[i] = v / sum(eigen_values) 17 | cumulative_variance_explained = np.cumsum(variance_explained) 18 | self.proj_matrix = eigen_vectors[:, :max(1, np.where(explain_ratio <= cumulative_variance_explained)[0][0])] 19 | print("Dimension reduced from %d to %d" % (len(eigen_values), self.proj_matrix.shape[1])) 20 | return 21 | 22 | def proj(self, x): 23 | return np.real(np.dot(x, self.proj_matrix)) 24 | 25 | 26 | if __name__ == "__main__": 27 | # Test 28 | X = np.random.random((5, 10)) 29 | print(X) 30 | pca = PCA(X, 0.85) 31 | X_pca = pca.proj(X) 32 | print(X_pca) 33 | -------------------------------------------------------------------------------- /svm.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from numpy.linalg import inv, pinv, eig 3 | import matplotlib.pyplot as plt 4 | from sklearn.metrics import confusion_matrix 5 | from sklearn.model_selection import train_test_split 6 | from sklearn import svm, datasets 7 | 8 | from pca import * 9 | from file_operate import * 10 | from utils import * 11 | 12 | 13 | class SVM_Attacker: 14 | ''' 15 | An implementation of SVM Attack on 1 Byte of AES, the leak model is Hamming Weight by default. 16 | ''' 17 | clf = None 18 | leak_model = None 19 | leak_range = None 20 | 21 | def __init__(self, traces, plain_texts, real_key, leak_model=HW): 22 | self.leak_model = leak_model 23 | self.leak_range = max(leak_model) + 1 24 | labels = [leak_model[SBOX[pt ^ real_key]] for pt in plain_texts] 25 | labels = np.asarray(labels) 26 | self.clf = svm.SVC(kernel='linear', probability=True, C=1, decision_function_shape='ovo').fit(traces, labels) 27 | print("The SVM template has been created.") 28 | 29 | def attack(self, traces, plaintexts): 30 | probs = self.clf.predict_proba(traces) 31 | score = np.zeros(256) 32 | for prob, plaintext in zip(probs, plaintexts): 33 | for k in range(256): 34 | mid = self.leak_model[SBOX[plaintext ^ k]] 35 | score[k] += PRE[mid] * prob[mid] 36 | print("Key found: %d" % score.argsort()[-1]) 37 | 38 | 39 | if __name__ == '__main__': 40 | # Setting for data operation, the REAL KEY is 66 41 | filename = r'mega128a5V4M_origin' 42 | path = r'./data' 43 | trace_num = 10000 44 | train_key = 66 45 | 46 | # Transfer trs to npz 47 | trs2Npz(path, filename, filename, trace_num) 48 | target = np.load(path + '\\' + filename + '.npz') 49 | raw_traces = target["trace"] 50 | plaintexts = target["crypto_data"] 51 | 52 | # Normalization on raw data traces 53 | traces = standardize(raw_traces) 54 | 55 | # If you need PCA, uncomment this 56 | pca = PCA(traces, explain_ratio=0.95) 57 | traces = pca.proj(traces) 58 | 59 | # Train set 60 | num_train = 9800 61 | train_tr = traces[:num_train, :] 62 | train_pt = plaintexts[:num_train] 63 | # Attack set 64 | attack_tr = traces[num_train:, :] 65 | attack_pt = plaintexts[num_train:] 66 | 67 | # Attack 68 | svm_ta = SVM_Attacker(traces=train_tr, plain_texts=train_pt, real_key=train_key) 69 | svm_ta.attack(attack_tr, attack_pt) 70 | -------------------------------------------------------------------------------- /rf.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from numpy.linalg import inv, pinv, eig 3 | import matplotlib.pyplot as plt 4 | from sklearn.metrics import confusion_matrix 5 | from sklearn.model_selection import train_test_split 6 | from sklearn.ensemble import RandomForestClassifier 7 | 8 | from pca import * 9 | from file_operate import * 10 | from utils import * 11 | 12 | 13 | class RF_Attacker: 14 | ''' 15 | An implementation of Random Forest Attack on 1 Byte of AES, the leak model is Hamming Weight by default. 16 | ''' 17 | clf = None 18 | leak_model = None 19 | leak_range = None 20 | 21 | def __init__(self, traces, plain_texts, real_key, leak_model=HW): 22 | self.leak_model = leak_model 23 | self.leak_range = max(leak_model) + 1 24 | labels = [leak_model[SBOX[pt ^ real_key]] for pt in plain_texts] 25 | labels = np.asarray(labels) 26 | self.clf = RandomForestClassifier(n_estimators=100) 27 | self.clf.fit(traces, labels) 28 | print("The random forest template has been created.") 29 | 30 | def attack(self, traces, plaintexts): 31 | probs = self.clf.predict_proba(traces) 32 | score = np.zeros(256) 33 | for prob, plaintext in zip(probs, plaintexts): 34 | for k in range(256): 35 | mid = self.leak_model[SBOX[plaintext ^ k]] 36 | score[k] += PRE[mid] * prob[mid] 37 | 38 | print("Key found: %d" % score.argsort()[-1]) 39 | 40 | 41 | if __name__ == '__main__': 42 | # Setting for data operation, the REAL KEY is 66 43 | filename = r'mega128a5V4M_origin' 44 | path = r'./data' 45 | trace_num = 10000 46 | train_key = 66 47 | 48 | # Transfer trs to npz 49 | trs2Npz(path, filename, filename, trace_num) 50 | target = np.load(path + '\\' + filename + '.npz') 51 | raw_traces = target["trace"] 52 | plaintexts = target["crypto_data"] 53 | 54 | # Normalization on raw data traces 55 | traces = standardize(raw_traces) 56 | 57 | # If you need PCA, uncomment this 58 | pca = PCA(traces, explain_ratio=0.95) 59 | traces = pca.proj(traces) 60 | 61 | # Train set 62 | num_train = 9800 63 | train_tr = traces[:num_train, :] 64 | train_pt = plaintexts[:num_train] 65 | # Attack set 66 | attack_tr = traces[num_train:, :] 67 | attack_pt = plaintexts[num_train:] 68 | 69 | # Attack 70 | svm_ta = RF_Attacker(traces=train_tr, plain_texts=train_pt, real_key=train_key) 71 | svm_ta.attack(attack_tr, attack_pt) 72 | -------------------------------------------------------------------------------- /cpa.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import logging 5 | 6 | from scipy.stats.stats import pearsonr 7 | from scipy import signal 8 | from file_operate import * 9 | from utils import * 10 | 11 | LOG_FORMAT = LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" 12 | logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) 13 | 14 | 15 | class CPA: 16 | traces = '' 17 | traceNum = '' 18 | signalLength = 0 19 | plainTexts = [] 20 | LeakModel = None 21 | 22 | def __init__(self, traces, plain_texts, leakModel=HW): 23 | self.traces = traces 24 | self.traceNum = np.shape(traces)[0] 25 | self.signalLength = np.shape(traces)[1] 26 | self.plainTexts = plain_texts 27 | self.LeakModel = np.array(leakModel) 28 | self.LeakModel = self.LeakModel.reshape(-1) 29 | 30 | def getKey(self, numBytes=1): 31 | keys = np.zeros(numBytes) 32 | hws = np.zeros(self.traceNum) 33 | pccs = np.zeros(self.signalLength) 34 | cpa = np.zeros(256) 35 | transTraces = np.transpose(self.traces) 36 | # pge = np.zeros(numBytes) 37 | # for i in range(numBytes): 38 | 39 | for guess in range(256): 40 | for j in range(self.traceNum): 41 | input = self.plainTexts[j] ^ guess 42 | input = SBOX[input] 43 | hws[j] = self.LeakModel[input] 44 | for j in range(self.signalLength): 45 | pccs[j] = self.getPCC(transTraces[j], hws) 46 | cpa[guess] = np.max(abs(pccs)) 47 | # logging.info("Guess %d" % guess) 48 | keys[0] = np.argmax(cpa) 49 | logging.info("Done %d byte 1") 50 | return keys, cpa 51 | 52 | def getPCC(self, X, Y): 53 | # return np.corrcoef(X,Y)[0,1] 54 | return pearsonr(X, Y)[0] 55 | 56 | 57 | def align(trace0, tracei): 58 | correlation = signal.correlate(tracei ** 2, trace0 ** 2) 59 | shift = np.argmax(correlation) - (len(trace0) - 1) 60 | return shift 61 | 62 | 63 | if __name__ == '__main__': 64 | # Setting for data operation, the REAL KEY is 66 65 | filename = r'mega128a5V4M_origin' 66 | path = r'./data' 67 | trace_num = 100000 68 | 69 | # Transfer trs to npz 70 | trs2Npz(path, filename, filename, trace_num) 71 | target = np.load(path + '\\' + filename + '.npz') 72 | 73 | # Trace set 74 | raw_traces = target["trace"] 75 | raw_plaintexts = target["crypto_data"] 76 | 77 | # Process the raw_trace with a naive alignment 78 | traces = raw_traces 79 | plaintexts = raw_plaintexts 80 | 81 | if False: # change this if you need alignment 82 | traces, plaintexts = cor_align(raw_traces, raw_plaintexts) 83 | 84 | plot_sample(raw_traces, 'raw') 85 | plot_sample(traces, 'aligned') 86 | # exit() 87 | 88 | logging.info("Toal traces: " + str(len(traces))) 89 | 90 | attacker = CPA(traces=traces, plain_texts=plaintexts) 91 | keys, cpa = attacker.getKey(numBytes=1) 92 | 93 | plt.plot(cpa) 94 | # plt.axvline(real_keys[0][0],color='r', linestyle='--',alpha=0.3) 95 | plt.title('Correlation distribution of 1st Byte.') 96 | plt.show() 97 | 98 | print("Recovered key: " + str(keys)) 99 | # print("Real key: " + str(real_keys[0])) 100 | # print(pge) 101 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import logging 4 | 5 | from scipy.stats import multivariate_normal, linregress, norm, pearsonr, entropy 6 | from scipy import signal 7 | 8 | SBOX = [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 | PRE = [1 / 256, 8 / 256, 28 / 256, 56 / 256, 70 / 256, 56 / 256, 28 / 256, 8 / 256, 1 / 256] 26 | 27 | HW = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 28 | 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 29 | 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 30 | 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 31 | 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 32 | 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 33 | 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 34 | 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8] 35 | 36 | 37 | def standardize(traces): 38 | mu = np.average(traces, 1) 39 | std = np.std(traces, 1) 40 | if np.min(std) != 0: 41 | traces = (traces - mu.reshape(mu.shape[0], 1)) / std.reshape(mu.shape[0], 1) 42 | return traces 43 | 44 | 45 | def cov(x, y): 46 | # Find the covariance between two 1D lists (x and y). 47 | # Note that var(x) = cov(x, x) 48 | return pearsonr(x, y)[0] 49 | 50 | 51 | def plot_sample(data, title=""): 52 | idx = [0, 1, 2, 3, 4] # np.random.choice(len(data),5,replace=False) 53 | for i in range(5): 54 | plt.subplot(5, 1, i + 1) 55 | plt.plot(data[idx[i]]) 56 | # plt.title(title) 57 | plt.show() 58 | 59 | 60 | def cor_align(raw_traces, raw_plaintexts): 61 | # Change this to your POI 62 | start = 500 63 | end = 1500 64 | traces = [] 65 | plaintexts = [] 66 | for i in range(len(raw_traces)): 67 | correlation = signal.correlate(raw_traces[i] ** 2, raw_traces[0] ** 2) 68 | s = np.argmax(correlation) - (len(raw_traces[0]) - 1) 69 | traces.append(raw_traces[i][start + s:end + s]) 70 | plaintexts.append(raw_plaintexts[i]) 71 | return np.stack(traces, axis=0), np.array(plaintexts) 72 | -------------------------------------------------------------------------------- /ta.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import matplotlib.pyplot as plt 3 | 4 | from pca import * 5 | from file_operate import * 6 | from utils import * 7 | 8 | 9 | class TA: 10 | ''' 11 | An implementation of TA on 1 Byte of AES, the leak model is Hamming Weight by default. 12 | ''' 13 | leak_model = None 14 | leak_range = None 15 | pois = None 16 | mean_matrix = None 17 | cov_matrix = None 18 | 19 | def __init__(self, traces, plain_texts, real_key, num_pois, leak_model=HW, poi_spacing=5): 20 | [trace_num, trace_point] = traces.shape 21 | self.leak_range = max(leak_model) + 1 22 | self.leak_model = leak_model 23 | self.mean_matrix = np.zeros((self.leak_range, num_pois)) 24 | self.cov_matrix = np.zeros((self.leak_range, num_pois, num_pois)) 25 | temp_SBOX = [SBOX[plain_texts[i] ^ real_key] for i in range(trace_num)] 26 | temp_lm = [leak_model[s] for s in temp_SBOX] 27 | # Sort traces by HW 28 | # Make self.leak_range blank lists - one for each Hamming weight 29 | temp_traces_lm = [[] for _ in range(self.leak_range)] 30 | # Fill them up 31 | for i, trace in enumerate(traces): 32 | temp_traces_lm[temp_lm[i]].append(trace) 33 | for mid in range(self.leak_range): 34 | assert len(temp_traces_lm[ 35 | mid]) != 0, "No trace with leak model value = %d, try increasing the number of traces" % mid 36 | # Switch to numpy arrays 37 | temp_traces_lm = [np.array(temp_traces_lm[_]) for _ in range(self.leak_range)] 38 | # Find averages 39 | tempMeans = np.zeros((self.leak_range, trace_point)) 40 | for mid in range(self.leak_range): 41 | tempMeans[mid] = np.average(temp_traces_lm[mid], 0) 42 | # Find sum of differences 43 | tempSumDiff = np.zeros(trace_point) 44 | for i in range(self.leak_range): 45 | for j in range(i): 46 | tempSumDiff += np.abs(tempMeans[i] - tempMeans[j]) 47 | # Find POIs 48 | self.pois = [] 49 | for i in range(num_pois): 50 | # Find the max 51 | nextPOI = tempSumDiff.argmax() 52 | self.pois.append(nextPOI) 53 | # Make sure we don't pick a nearby value 54 | 55 | poiMin = max(0, nextPOI - poi_spacing) 56 | poiMax = min(nextPOI + poi_spacing, len(tempSumDiff)) 57 | for j in range(poiMin, poiMax): 58 | tempSumDiff[j] = 0 59 | # Fill up mean and covariance matrix for each HW 60 | self.mean_matrix = np.zeros((self.leak_range, num_pois)) 61 | self.cov_matrix = np.zeros((self.leak_range, num_pois, num_pois)) 62 | for mid in range(self.leak_range): 63 | for i in range(num_pois): 64 | # Fill in mean 65 | self.mean_matrix[mid][i] = tempMeans[mid][self.pois[i]] 66 | for j in range(num_pois): 67 | x = temp_traces_lm[mid][:, self.pois[i]] 68 | y = temp_traces_lm[mid][:, self.pois[j]] 69 | self.cov_matrix[mid, i, j] = cov(x, y) 70 | print("The template has been created.") 71 | return 72 | 73 | def attack(self, traces, plaintext): 74 | rank_key = np.zeros(256) 75 | for j, trace in enumerate(traces): 76 | # Grab key points and put them in a small matrix 77 | a = [trace[poi] for poi in self.pois] 78 | 79 | # Test each key 80 | for k in range(256): 81 | # Find leak model coming out of sbox 82 | mid = self.leak_model[SBOX[plaintext[j] ^ k]] 83 | 84 | # Find p_{k,j} 85 | # print(np.linalg.det(self.cov_matrix[mid])) 86 | rv = multivariate_normal(self.mean_matrix[mid], self.cov_matrix[mid], allow_singular=True) 87 | p_kj = PRE[mid] * rv.pdf(a) 88 | 89 | # Add it to running total 90 | rank_key[k] += np.log(p_kj) 91 | 92 | guessed = rank_key.argsort()[-1] 93 | print("Key found: %d" % guessed) 94 | return self.mean_matrix, self.cov_matrix, guessed 95 | 96 | 97 | if __name__ == '__main__': 98 | # Setting for data operation, the REAL KEY is 66 99 | filename = r'mega128a5V4M_origin' 100 | path = r'./data' 101 | trace_num = 10000 102 | train_key = 66 103 | 104 | # Transfer trs to npz 105 | trs2Npz(path, filename, filename, trace_num) 106 | target = np.load(path + '\\' + filename + '.npz') 107 | raw_traces = target["trace"] 108 | plaintexts = target["crypto_data"] 109 | 110 | # Normalization on raw data traces 111 | traces = standardize(raw_traces) 112 | 113 | # If you need PCA, uncomment this 114 | pca = PCA(traces, explain_ratio=0.95) 115 | traces = pca.proj(traces) 116 | 117 | # Train set 118 | num_train = 9800 119 | train_tr = traces[:num_train, :] 120 | train_pt = plaintexts[:num_train] 121 | # Attack set 122 | attack_tr = traces[num_train:, :] 123 | attack_pt = plaintexts[num_train:] 124 | 125 | # Get a TA attacker 126 | ta = TA(traces=train_tr, plain_texts=train_pt, real_key=train_key, num_pois=5) 127 | mean_matrix, cov_matrix, guessed = ta.attack(attack_tr, attack_pt) 128 | -------------------------------------------------------------------------------- /fisher.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | from numpy.linalg import inv, pinv, eig 3 | import matplotlib.pyplot as plt 4 | 5 | from pca import * 6 | from file_operate import * 7 | from utils import * 8 | 9 | 10 | class LDAClassifier: 11 | 12 | def __init__(self, projection_dim): 13 | self.projection_dim = projection_dim 14 | self.W = None # weights 15 | self.g_means, self.g_covariance, self.priors = None, None, None 16 | 17 | def fit(self, X): 18 | means_k = self.__compute_means(X) 19 | 20 | Sks = [] 21 | for class_i, m in means_k.items(): 22 | sub = np.subtract(X[class_i], m) 23 | Sks.append(np.dot(np.transpose(sub), sub)) 24 | 25 | Sks = np.asarray(Sks) 26 | Sw = np.sum(Sks, axis=0) # shape = (D,D) 27 | 28 | Nk = {} 29 | sum_ = 0 30 | for class_id, data in X.items(): 31 | Nk[class_id] = data.shape[0] 32 | sum_ += np.sum(data, axis=0) 33 | 34 | self.N = sum(list(Nk.values())) 35 | 36 | # m is the mean of the total data set 37 | m = sum_ / self.N 38 | 39 | SB = [] 40 | for class_id, mean_class_i in means_k.items(): 41 | sub_ = mean_class_i - m 42 | SB.append(np.multiply(Nk[class_id], np.outer(sub_, sub_.T))) 43 | 44 | # between class covariance matrix shape = (D,D). D = input vector dimensions 45 | SB = np.sum(SB, axis=0) # sum of K (# of classes) matrices 46 | 47 | matrix = np.dot(pinv(Sw), SB) 48 | # find eigen values and eigen-vectors pairs for np.dot(pinv(SW),SB) 49 | eigen_values, eigen_vectors = eig(matrix) 50 | 51 | eiglist = [(eigen_values[i], eigen_vectors[:, i]) for i in range(len(eigen_values))] 52 | 53 | # sort the eigvals in decreasing order 54 | eiglist = sorted(eiglist, key=lambda x: x[0], reverse=True) 55 | 56 | # take the first num_dims eigvectors 57 | self.W = np.array([eiglist[i][1] for i in range(self.projection_dim)]) 58 | self.W = np.asarray(self.W).T 59 | 60 | # get parameter of the Gaussian distribution 61 | self.g_means, self.g_covariance, self.priors = self.gaussian(X) 62 | 63 | # Returns the parameters of the Gaussian distributions 64 | def gaussian(self, X): 65 | means = {} 66 | covariance = {} 67 | priors = {} # p(Ck) 68 | for class_id, values in X.items(): 69 | proj = np.dot(values, self.W) 70 | means[class_id] = np.mean(proj, axis=0) 71 | covariance[class_id] = np.cov(proj, rowvar=False) 72 | priors[class_id] = values.shape[0] / self.N 73 | return means, covariance, priors 74 | 75 | # model a multi-variate Gaussian distribution for each class’ likelihood distribution P(x|Ck) 76 | def gaussian_distribution(self, x, u, cov): 77 | scalar = (1. / ((2 * np.pi) ** (x.shape[0] / 2.))) * (1 / np.sqrt(np.linalg.det(cov))) 78 | x_sub_u = np.subtract(x, u) 79 | return scalar * np.exp(-np.dot(np.dot(x_sub_u, inv(cov)), x_sub_u.T) / 2.) 80 | 81 | def prob(self, x, y): 82 | return self.priors[y] * self.gaussian_distribution(self.project(x), self.g_means[y], self.g_covariance[y]) 83 | 84 | def score(self, X, y): 85 | proj = self.project(X) 86 | gaussian_likelihoods = [] 87 | classes = sorted(list(self.g_means.keys())) 88 | for x in proj: 89 | row = [] 90 | for c in classes: # number of classes 91 | res = self.priors[c] * self.gaussian_distribution(x, self.g_means[c], self.g_covariance[ 92 | c]) # Compute the posterios P(Ck|x) prob of a class k given a point x 93 | row.append(res) 94 | 95 | gaussian_likelihoods.append(row) 96 | 97 | gaussian_likelihoods = np.asarray(gaussian_likelihoods) 98 | 99 | # assign x to the class with the largest posterior probability 100 | predictions = np.argmax(gaussian_likelihoods, axis=1) 101 | return np.sum(predictions == y) / len(y), predictions, proj 102 | 103 | def project(self, X): 104 | return np.dot(X, self.W) 105 | 106 | def __compute_means(self, X): 107 | # Compute the means for each class k=1,2,3...K 108 | # If the dataset has K classes, then, self.means_k.shape = [# of records, K] 109 | means_k = {} 110 | for class_i, input_vectors in X.items(): 111 | means_k[class_i] = np.mean(input_vectors, axis=0) 112 | return means_k 113 | 114 | 115 | class LDAAttacker: 116 | ''' 117 | An implementation of Fisher-LDA Attack on 1 Byte of AES, the leak model is Hamming Weight by default. 118 | ''' 119 | clf = None 120 | leak_model = None 121 | leak_range = None 122 | 123 | def __init__(self, traces, plain_texts, real_key, leak_model=HW): 124 | self.leak_model = leak_model 125 | self.leak_range = max(leak_model) + 1 126 | data_dict = {} 127 | for x, pt in zip(traces, plain_texts): 128 | # Get Hamming Weight 129 | y = leak_model[SBOX[pt ^ real_key]] 130 | if y not in data_dict: 131 | data_dict[y] = [x.flatten()] 132 | else: 133 | data_dict[y].append(x.flatten()) 134 | 135 | for i in range(self.leak_range): 136 | data_dict[i] = np.asarray(data_dict[i]) 137 | 138 | self.clf = LDAClassifier(self.leak_range) 139 | self.clf.fit(data_dict) 140 | print("The Fisher-LDA template has been created.") 141 | 142 | def attack(self, traces, plaintexts): 143 | score = np.zeros(256) 144 | for trace, plaintext in zip(traces, plaintexts): 145 | for k in range(256): 146 | score[k] += np.real(self.clf.prob(trace, self.leak_model[SBOX[plaintext ^ k]])) 147 | print("Key found: %d" % score.argsort()[-1]) 148 | 149 | 150 | if __name__ == '__main__': 151 | # Setting for data operation, the REAL KEY is 66 152 | filename = r'mega128a5V4M_origin' 153 | path = r'./data' 154 | trace_num = 10000 155 | train_key = 66 156 | 157 | # Transfer trs to npz 158 | trs2Npz(path, filename, filename, trace_num) 159 | target = np.load(path + '\\' + filename + '.npz') 160 | raw_traces = target["trace"] 161 | plaintexts = target["crypto_data"] 162 | 163 | # Normalization on raw data traces 164 | traces = standardize(raw_traces) 165 | 166 | # Train set 167 | num_train = 9800 168 | train_tr = traces[:num_train, :] 169 | train_pt = plaintexts[:num_train] 170 | # Attack set 171 | attack_tr = traces[num_train:, :] 172 | attack_pt = plaintexts[num_train:] 173 | 174 | # Attack 175 | fish = LDAAttacker(traces=train_tr, plain_texts=train_pt, real_key=train_key) 176 | fish.attack(attack_tr, attack_pt) 177 | -------------------------------------------------------------------------------- /file_operate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # # 将采集的trs文件转为numpy常用的npz格式 5 | 6 | 7 | # convert trs to npy 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | import os 11 | # get_ipython().run_line_magic('matplotlib', 'inline') 12 | 13 | import logging 14 | import numpy as np 15 | from struct import pack, unpack 16 | 17 | 18 | def progress(count, total, job_name=''): 19 | """Print the colored progress bar""" 20 | 21 | if count != (total - 1): 22 | if count % (int(total / 10)) != 0: 23 | return 24 | width = 50 25 | percents = int(round(100.0 * count / float(total), 1)) 26 | end = '' 27 | status = "Running..." 28 | if percents == 100: 29 | end = '\n' 30 | status = 'Finished.' 31 | # high contras blue is 96 in 8 basic color system 32 | bar = '\x1b[{};1m{}\x1b[0m'.format(96, (int(percents / 2)) * '#') 33 | white = (width - int(percents / 2)) * ' ' 34 | out_str = '\r[{}{}] \x1b[94;1m{}% \x1b[93;1m{} {}\x1b[0m'.format(bar, white, percents, job_name, status) 35 | print(out_str, end=end) 36 | 37 | 38 | class CommonFile(object): 39 | __filePath = '' 40 | __byteNum = 0 41 | __fileHandler = None 42 | 43 | def __init__(self, filePath): 44 | self.__filePath = filePath 45 | 46 | @property 47 | def byteNum(self): 48 | return self.__byteNum 49 | 50 | def openFile(self, mode): 51 | self.__fileHandler = open(self.__filePath, mode) 52 | 53 | def readbyte(self, num): 54 | byte_re = self.__fileHandler.read(num) 55 | self.__byteNum += num 56 | return byte_re 57 | 58 | def readint(self, num=4): 59 | byte_re = self.__fileHandler.read(num) 60 | self.__byteNum += num 61 | return int.from_bytes(byte_re, 'little') 62 | 63 | def readstr(self, num): 64 | byte_re = self.__fileHandler.read(num) 65 | self.__byteNum += num 66 | return byte_re.decode() 67 | 68 | def readfloat(self, num=4): 69 | byte_re = self.__fileHandler.read(num) 70 | self.__byteNum += num 71 | return float.fromhex(byte_re.hex()) 72 | 73 | def seekfile(self, num=0): 74 | self.__fileHandler.seek(num, 0) 75 | return True 76 | 77 | def writeByte(self, value): 78 | self.__fileHandler.write(value) 79 | 80 | def closeFile(self): 81 | self.__fileHandler.close() 82 | 83 | 84 | class TrsHandler(object): 85 | """.trs file handler class""" 86 | 87 | __path = '' 88 | __traceFile = None 89 | __traceHeaderFun = None 90 | 91 | __TraceHeader = {} 92 | __headerLength = 0 93 | __traceNumber = -1 94 | __pointCount = -1 95 | __sampleCoding = -1 96 | __sampleLength = 0 97 | __cryptoDataLength = 0 98 | __titleSpace = 0 99 | __globalTraceTitle = 'SCAStudio' 100 | __description = None 101 | __xAxisOffset = 0 102 | __xLabel = '' 103 | __yLabel = '' 104 | __xAxisScale = 0 105 | __yAxisScale = 0 106 | __traceOffsetForDisp = 0 107 | __logScale = 0 108 | 109 | def __init__(self, path): 110 | self.__path = path 111 | self.__traceFile = CommonFile(path) 112 | self.__traceHeaderFun = {b'\x41': self.__NT, b'\x42': self.__NS, b'\x43': self.__SC, b'\x44': self.__DS, 113 | b'\x45': self.__TS, 114 | b'\x46': self.__GT, b'\x47': self.__DC, b'\x48': self.__XO, b'\x49': self.__XL, 115 | b'\x4A': self.__YL, 116 | b'\x4B': self.__XS, b'\x4C': self.__YS, b'\x4D': self.__TO, b'\x4E': self.__LS, 117 | b'\x5F': self.__TB, 118 | b'\x55': self.__UN, b'\x04': self.__UN, b'\x9A': self.__UN} 119 | 120 | @property 121 | def filePath(self): 122 | return self.__path 123 | 124 | @filePath.setter 125 | def filePath(self, value): 126 | self.__path = value 127 | 128 | @property 129 | def traceNumber(self): 130 | return self.__traceNumber 131 | 132 | @traceNumber.setter 133 | def traceNumber(self, value): 134 | self.__traceNumber = value 135 | 136 | @property 137 | def pointNumber(self): 138 | return self.__pointCount 139 | 140 | @pointNumber.setter 141 | def pointNumber(self, value): 142 | self.__pointCount = value 143 | 144 | @property 145 | def sampleCoding(self): 146 | return self.__sampleCoding 147 | 148 | @sampleCoding.setter 149 | def sampleCoding(self, value): 150 | self.__sampleCoding = value 151 | 152 | @property 153 | def sampleLength(self): 154 | return self.__sampleLength 155 | 156 | @sampleLength.setter 157 | def sampleLength(self, value): 158 | self.__sampleLength = value 159 | 160 | @property 161 | def cryptoDataCount(self): 162 | return self.__cryptoDataLength 163 | 164 | @cryptoDataCount.setter 165 | def cryptoDataCount(self, value): 166 | self.__cryptoDataLength = value 167 | 168 | @property 169 | def title_space(self): 170 | return self.__titleSpace 171 | 172 | @title_space.setter 173 | def title_space(self, value): 174 | self.__titleSpace = value 175 | 176 | @property 177 | def header_length(self): 178 | return self.__headerLength 179 | 180 | def parseFileHeader(self): 181 | logging.debug('Start Parsing Trace Header') 182 | self.__traceFile.openFile('rb') 183 | while True: 184 | ch = self.__traceFile.readbyte(1) 185 | logging.debug('Parsing Trace Header : ' + ch.hex()) 186 | try: 187 | self.__traceHeaderFun[ch]() 188 | except KeyError: 189 | print("Key Error: Invalid Trs File Format.") 190 | break 191 | except ValueError: 192 | print("Value Error: Invalid Trs File Format.") 193 | break 194 | if ch == b'\x5F': 195 | logging.debug('Parsing Trace Header Finished') 196 | break 197 | self.__traceFile.closeFile() 198 | 199 | def generateTraceHeader(self): 200 | traceHeader = b'\x41\x04' 201 | traceHeader += self.__traceNumber.to_bytes(4, 'little') 202 | traceHeader += b'\x42\x04' 203 | traceHeader += self.__pointCount.to_bytes(4, 'little') 204 | traceHeader += b'\x43\x01' 205 | if self.__sampleCoding == 0: 206 | traceHeader += self.__sampleLength.to_bytes(1, 'little') 207 | else: 208 | traceHeader += (self.__sampleLength | 0x10).to_bytes(1, 'little') 209 | 210 | traceHeader += b'\x44\x02' 211 | traceHeader += self.__cryptoDataLength.to_bytes(2, 'little') 212 | traceHeader += b'\x5F\x00' 213 | 214 | self.__traceFile.openFile('wb') 215 | self.__traceFile.writeByte(traceHeader) 216 | self.__traceFile.closeFile() 217 | 218 | def generateTrace(self, point, cryptoData=None, title=None): 219 | traceStr = b'' 220 | self.__traceFile.openFile('ab+') 221 | if title is not None: 222 | traceStr += title.encode('utf8') 223 | # self.__traceFile.writeByte(title.encode('utf8')) 224 | if cryptoData is not None: 225 | traceStr += bytes(cryptoData) 226 | # self.__traceFile.writeByte(bytes(cryptoData)) 227 | if self.__sampleCoding == 0: 228 | if self.__sampleLength == 1: 229 | traceStr += bytes(point) 230 | elif self.__sampleLength == 2: 231 | for i in point: 232 | traceStr += pack(' self.__traceNumber - 1: 382 | logging.error('Wrong Trace Index') 383 | raise ValueError('Wrong Trace Index') 384 | 385 | samplePoint = () 386 | traceTitle = '' 387 | cryptoData = None 388 | self.__traceFile.openFile('rb') 389 | self.__traceFile.seekfile(self.__headerLength + index * ( 390 | self.__titleSpace + self.__cryptoDataLength + self.__pointCount * self.__sampleLength)) 391 | if self.__titleSpace != 0: 392 | traceTitle = self.__traceFile.readstr(self.titleSpace).decode('utf-8') 393 | logging.debug('Trace %d title : %s' % (index, traceTitle)) 394 | if self.__cryptoDataLength != 0: 395 | cryptoData = list(self.__traceFile.readbyte(self.__cryptoDataLength)) 396 | logging.debug('CryptoData:' + str(cryptoData)) 397 | if self.__pointCount != 0: 398 | if self.__sampleCoding == 0: 399 | bstr = self.__traceFile.readbyte(self.__sampleLength * self.__pointCount) 400 | # print(index) 401 | if self.__sampleLength == 1: 402 | samplePoint = unpack(str(self.__pointCount) + 'B', bstr) 403 | elif self.sampleLength == 2: 404 | samplePoint = unpack('<' + str(self.__pointCount) + 'H', bstr) 405 | elif self.sampleLength == 4: 406 | samplePoint = unpack('<' + str(self.__pointCount) + 'I', bstr) 407 | else: 408 | bstr = self.__traceFile.readbyte(self.__sampleLength * self.__pointCount) 409 | samplePoint = unpack('<' + str(self.__pointCount) + 'f', bstr) 410 | 411 | self.__traceFile.closeFile() 412 | 413 | return [samplePoint, cryptoData, traceTitle] 414 | 415 | def get_trace_npy(self, index_range=None): 416 | if index_range is None: 417 | index_range = np.arange(0, self.traceNumber) 418 | trace_count = index_range.shape[0] 419 | sample_mat = np.zeros((trace_count, self.pointNumber)) 420 | 421 | for index in range(trace_count): 422 | sample_mat[index, :], _, _ = self.getTrace(index_range[index]) 423 | progress(index, trace_count, 'Extract Sample') 424 | 425 | return sample_mat 426 | 427 | def get_crypto_data_npy(self, index_range=None): 428 | if index_range is None: 429 | index_range = np.arange(0, self.traceNumber) 430 | trace_count = index_range.shape[0] 431 | crypto_data_mat = np.zeros((trace_count, self.cryptoDataCount), dtype=np.uint8) 432 | for index in range(trace_count): 433 | _, crypto_data_mat[index, :], _ = self.getTrace(index_range[index]) 434 | progress(index, trace_count, 'Extract Crypto Data') 435 | return crypto_data_mat 436 | 437 | def __str__(self): 438 | return "cryptoDataCount = {0}\nsampleLength = {1}\nsampleCoding = {2}\n" \ 439 | "pointNumber = {3}\ntraceNumber = {4}\ntraceFile = {5}".format(self.__cryptoDataLength, 440 | self.__sampleLength, self.__sampleCoding, 441 | self.__pointCount, self.__traceNumber, 442 | self.__path) 443 | 444 | 445 | def trs2Npz(data_file_path, trs_name, npz_name, trace_num): 446 | ''' 447 | 该函数将trs文件转化为npz格式 448 | :param data_file_path: 目标文件位置 示例:data_file_path = r'G:\lowpass' 449 | :param trs_name: 曲线文件名 示例:trs_name = r'\attiny161433V2M + LowPass' 450 | :param npz_name: 需要保存的npz目标文件名 示例:npz_name = r'\attiny161433V2M' 451 | :param trace_num: 曲线条数 示例:trace_num=110000 452 | :return: null 453 | ''' 454 | for dirpath, dirnames, filenames in os.walk(data_file_path): 455 | file_name = filenames 456 | break 457 | if npz_name + '.npz' in file_name: 458 | print(npz_name + '.npz已经存在') 459 | return 460 | th = TrsHandler(data_file_path + '\\' + trs_name + '.trs') 461 | th.parseFileHeader() 462 | 463 | # 获得trs文件中的能量迹,np.arange(20000)意思是获取0-19999曲线,get_trace_npy可任意获取任意索引的曲线 464 | trace_mat = th.get_trace_npy(np.arange(trace_num)) 465 | 466 | # 获得trs文件中的明密文 467 | crypto_mat = th.get_crypto_data_npy(np.arange(trace_num)) 468 | #  保存 469 | np.savez(data_file_path + '\\' + npz_name + '.npz', trace=trace_mat, crypto_data=crypto_mat[:, 0]) 470 | 471 | 472 | def getIntermediateMatrix(data_file_path, npz_name): 473 | # # 计算中间值矩阵 474 | 475 | # 这里是示例,随机产生了明文,真正攻击时需要提取曲线中的明文 476 | 477 | # pt = np.random.randint(low=0, high=256, size=100000, dtype='uint8') 478 | 479 | # # Fixed key, 0x42 480 | # key = 0x42 481 | # # True Sbox In 482 | # sin = pt ^ key 483 | # target = np.load(r"G:\lowpass\attiny161433V2M") 484 | target = np.load(data_file_path + '\\' + npz_name + '.npz') 485 | pt = target["crypto_data"] 486 | # AES S盒 487 | sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 488 | 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 489 | 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 490 | 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 491 | 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 492 | 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 493 | 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 494 | 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 495 | 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 496 | 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 497 | 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 498 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 499 | 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 500 | 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 501 | 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 502 | 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16] 503 | 504 | sbox = np.array(sbox, dtype='uint8') 505 | 506 | # 汉明重量矩阵,把256种值的汉明重量存为了数组,方便使用 507 | hwmat = np.load('hwMat.npy') 508 | hwmat = hwmat.reshape(-1) 509 | 510 | # 根据明文,遍历可能的key,计算可能的S盒输入输出 511 | guess_sin = [] 512 | guess_sout = [] 513 | for key_guess in range(256): 514 | # 明文异或猜测key 515 | tmp = pt ^ key_guess 516 | guess_sin.append(tmp) 517 | # 得到对应的猜测S盒输出 518 | tmp = sbox[tmp] 519 | guess_sout.append(tmp) 520 | 521 | guess_sin_value = np.transpose(np.vstack(guess_sin)) 522 | guess_sin_hw = hwmat[guess_sin_value] 523 | 524 | # 得到猜测S盒输出的汉明重量 525 | guess_sout_value = np.transpose(np.vstack(guess_sout)) 526 | guess_sout_hw = hwmat[guess_sout_value] 527 | return [target, guess_sout_hw] 528 | --------------------------------------------------------------------------------