一、MLSCA原理介紹
基于機器學習的側信道分析(MLSCA)是一種結合傳統側信道分析技術與現代機器學習算法的密碼分析方法。該方法通過分析密碼設備運行時的物理泄漏信息(如功耗、電磁輻射等),利用機器學習模型建立泄漏數據與密鑰信息之間的關聯模型,從而實現對密鑰的恢復攻擊。與傳統分析方法相比,MLSCA能夠自動提取特征并處理復雜的非線性關系,顯著提高了攻擊效率。
MLSCA的核心思想是將側信道攻擊轉化為分類或回歸問題。在本文中,攻擊者針對AES算法的S盒輸出漢明重量構建分類模型。漢明重量反映了數據在硬件處理時的功耗特征,是側信道分析中最常用的泄漏模型之一。通過采集大量已知明文的功耗曲線,機器學習模型學習不同漢明重量對應的功耗模式特征,進而預測未知曲線對應的中間值,最終通過統計分析恢復出密鑰信息。
該方法通常包含兩個階段:模板構建階段和匹配攻擊階段。模板構建階段使用已知密鑰的功耗數據訓練機器學習模型,建立中間值與功耗特征的映射關系。匹配攻擊階段則利用訓練好的模型分析目標設備的未知功耗曲線,通過比較預測結果與假設中間值的匹配程度,逐步縮小密鑰候選范圍,最終確定最可能的密鑰值。支持向量機(SVC)等分類算法在此過程中表現出色,能夠有效處理高維非線性特征。
二、Python代碼實現
1. 數據加載與預處理函數
load_trs_to_dict
函數負責讀取trs格式的側信道數據文件,這種格式專門用于存儲能量跡數據。該函數將文件頭信息、能量跡曲線和附加參數分別存儲在字典結構中,便于后續處理。函數支持指定讀取的能量跡范圍,有效管理內存使用。
get_intermediate_values
函數根據AES算法的S盒操作生成中間值矩陣。它采用漢明重量作為泄漏模型,計算真實密鑰和256種假設密鑰對應的中間值。該函數返回兩個關鍵矩陣:實際中間值向量和假設中間值矩陣,為后續的機器學習分析提供基礎數據。
2. 機器學習核心組件
ProbCalculator
類實現了密鑰猜測的似然概率計算,采用對數似然累加方式評估各個密鑰假設的可能性。該類維護一個256元素的數組,記錄每個可能密鑰的累積得分,支持增量更新和結果查詢。
train_model
函數使用支持向量機(SVC)作為分類器,訓練模型建立能量跡特征與漢明重量標簽之間的映射關系。SVC模型啟用概率估計功能,可以輸出每個類別的預測概率,這對后續的密鑰排名計算至關重要。
3. 攻擊評估與分析函數
single_attack
函數執行單次攻擊實驗,通過分批處理能量跡數據并更新密鑰似然值,跟蹤真實密鑰的排名變化。該函數體現了MLSCA的核心攻擊流程,展示了如何利用訓練好的模型逐步縮小密鑰候選范圍。
guess_entropy
函數是綜合評估模塊,通過多次重復實驗計算猜測熵和成功率兩個關鍵指標。它自動記錄攻擊過程中密鑰排名的演變情況,并統計達到特定成功率所需的最小能量跡數量,為評估攻擊效果提供量化依據。
具體代碼如下:
"""
對AES算法的軟件實現進行基于機器學習的側信道分析(MLSCA)的Python代碼
泄漏模型:S盒輸出的漢明重量
運行前先激活Python環境,執行pip install numpy tqdm trsfile matplotlib scikit-learn命令安裝所需的庫
'AES_POWER_STM32F_NO-PROTECT_60000.trs'是功耗曲線文件,請點擊https://download.csdn.net/download/weixin_43261410/91057055免費下載
查看.trs格式文件介紹請瀏覽https://blog.csdn.net/weixin_43261410/article/details/148737286?fromshare=blogdetail&sharetype=blogdetail&sharerId=148737286&sharerefer=PC&sharesource=weixin_43261410&sharefrom=from_link
"""import os
import numpy as np
from sklearn.svm import SVC # 導入支持向量機
from tqdm import tqdm # 用于顯示進度條
from trsfile import trs_open # 用于讀取.trs格式的側信道數據文件
import matplotlib.pyplot as plt # 用于畫圖AES_SBOX = np.array([0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
], dtype=np.uint8)# 漢明重量查找表,用于快速計算一個字節的漢明重量(二進制中1的個數)
HW_ARRAY = np.array([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, 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, 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, 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, 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, 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, 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, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
])def load_trs_to_dict(trs_file_path: str, start_trace: int = None, end_trace: int = None):"""加載.trs文件到字典結構中參數:trs_file_path: .trs文件路徑start_trace: 起始能量曲線索引(可選)end_trace: 結束能量曲線索引(可選)返回:包含頭信息、能量曲線數據和額外參數的字典"""header, traces, param = {}, [], {}with trs_open(trs_file_path, 'r') as trs_file:# 讀取文件頭信息for key, value in trs_file.get_headers().items():header[key.name] = value# 處理能量曲線范圍number_traces = header['NUMBER_TRACES']start_trace = 0 if start_trace is None else min(start_trace, number_traces - 1)end_trace = number_traces if end_trace is None else min(end_trace, number_traces)header['NUMBER_TRACES'] = end_trace - start_trace# 初始化能量曲線數據數組traces = np.zeros((header['NUMBER_TRACES'], header['NUMBER_SAMPLES']), dtype=np.float32)# 初始化參數數組param = {}for key in trs_file[0].parameters:param_len = len(trs_file[0].parameters[key].value)param[key] = np.zeros((header['NUMBER_TRACES'], param_len), dtype=np.uint8)# 讀取能量曲線數據和參數for i, trace in enumerate(trs_file[start_trace:end_trace]):for key in trace.parameters:param[key][i] = trace.parameters[key].valuetraces[i] = np.array(trace.samples)# 返回結構化數據trs_dict = {"HEADER": header,"TRACES": traces,"PARAM": param}return trs_dictdef get_intermediate_values(plaintext, target_byte, real_key):"""生成中間值矩陣(基于漢明重量模型)參數:plaintext: 明文數組 (N x 16)target_byte: 目標字節位置(0-15)real_key: 真實密鑰字節返回:actual: 真實中間值向量(長度等于能量曲線數量)hypo: 假設中間值矩陣(能量曲線數量 × 256種密鑰假設)"""# 計算真實的S盒輸入actual_sbox_in = plaintext[:, target_byte] ^ real_key# 計算S盒輸出actual_sbox_out = AES_SBOX[actual_sbox_in]# 計算漢明重量作為功耗模型actual = HW_ARRAY[actual_sbox_out]# 生成256種可能的密鑰假設hypo_key = np.array(range(256), np.uint8)# 計算S盒輸入hypo_sbox_in = np.array([pt ^ hypo_key for pt in plaintext[:, target_byte]], np.uint8)# 計算S盒輸出hypo_sbox_out = AES_SBOX[hypo_sbox_in]# 將漢明重量作為功耗模型hypo = HW_ARRAY[hypo_sbox_out]return actual, hypoclass ProbCalculator():"""計算密鑰假設的似然概率"""def __init__(self):# 初始化密鑰假設的似然累加器,256個可能的密鑰假設self.guess_key_likelihood = np.zeros((256))def update(self, predict, hypo):"""更新似然值參數:predict: 當當前批次的概率值hypo: 當前批次的假設中間值"""# 獲取每個假設對應的概率值likelihood_matrix = np.array([predict[i, hypo[i]] for i in range(predict.shape[0])])# 處理概率為0的情況,避免對數運算出錯min_nzero = min(min(likelihood_matrix[likelihood_matrix != 0]), 0.01)likelihood_matrix = np.where(likelihood_matrix == 0, min_nzero ** 2, likelihood_matrix)# 計算對數似然并累加guess_key_likelihood = np.sum(np.log(likelihood_matrix), axis=0)self.guess_key_likelihood = self.guess_key_likelihood + guess_key_likelihooddef __call__(self):"""返回當前似然值向量"""return self.guess_key_likelihooddef train_model(traces, actual):"""訓練SVC模型參數:traces: 用于訓練SVC模型的能量曲線actual: 每條曲線對應的真實漢明重量返回:model: SVC模型"""# 使用支持向量機分類器,啟用概率估計model = SVC(probability=True, verbose=True)model.fit(traces, actual)return modeldef single_attack(predict_matrix, hypo, real_key, index):"""執行單次攻擊參數:predict_matrix: 模型預測的概率值數據hypo: 假設中間值數據real_key: 真實密鑰字節(用于評估攻擊效果)index: 分批處理的索引列表返回:key_rank_list: 每次更新后真實密鑰的排名列表"""# 初始化概率計算器probability = ProbCalculator()key_rank_list = []# 分批處理能量曲線數據for i in range(len(index) - 1):# 獲取當前批次的曲線和中間值start_idx = index[i]end_idx = index[i + 1]current_predict = predict_matrix[start_idx:end_idx]current_hypo = hypo[start_idx:end_idx]# 更新密鑰似然值probability.update(current_predict, current_hypo)# 獲取當前所有密鑰假設的似然值prob = probability()# 計算密鑰排名(似然值越小排名越高)prob_sort = np.argsort(np.argsort(-prob))# 記錄真實密鑰的當前排名key_rank = prob_sort[real_key]key_rank_list.append(key_rank)return key_rank_listdef guess_entropy(traces, hypo, model, real_key, result_path='result.txt', repeat_n=100, sample=500, header='', step=1):"""計算猜測熵和成功率參數:traces: 能量曲線數據hypo: 假設中間值數據model: SVC模型real_key: 真實密鑰字節result_path: 結果保存路徑repeat_n: 重復實驗次數sample: 每次實驗使用的能量曲線數量header: 結果文件頭部信息step: 能量曲線數量增加的步長"""def get_value_n(arr, value, traces_number, less):"""輔助函數: 獲取達到特定值所需的最少能量曲線數量"""if less:mask = arr <= valueelse:mask = arr >= valuevalid_index = np.cumprod(mask[::-1])[::-1]valid_index = np.where(valid_index)[0]return traces_number[valid_index[0]] if len(valid_index) > 0 else None# 設置分批索引列表index = [i for i in range(0, sample, step)]index.append(sample)# 準備隨機排列key_rank_arr = []permutations = np.zeros((repeat_n, sample), dtype=int)for i in range(repeat_n):permutations[i] = np.random.permutation(traces.shape[0])[:sample]# 獲取模型對所有能量曲線的預測結果predict_matrix = model.predict_proba(traces)# 多次重復實驗for i in tqdm(range(repeat_n)):predict_random = predict_matrix[permutations[i]]hypo_random = hypo[permutations[i]]key_rank_list = single_attack(predict_random, hypo_random, real_key, index)key_rank_arr.append(key_rank_list)key_rank_arr = np.array(key_rank_arr)traces_number = np.array(index[1:])# 計算猜測熵guess_entropy = np.mean(key_rank_arr, axis=0)guess_entropy_10 = get_value_n(guess_entropy, 10, traces_number, True)guess_entropy_1 = get_value_n(guess_entropy, 1, traces_number, True)guess_entropy_0 = get_value_n(guess_entropy, 0, traces_number, True)# 計算成功率success_rate = np.mean(key_rank_arr < 10, axis=0) # 排名小于10被認為攻擊成功success_rate_p9 = get_value_n(success_rate, 0.9, traces_number, False)success_rate_p99 = get_value_n(success_rate, 0.99, traces_number, False)success_rate_1 = get_value_n(success_rate, 1, traces_number, False)# 保存結果到文件mode = 'a' if os.path.exists(result_path) else 'w'with open(result_path, mode, encoding='utf-8') as f:f.write(header)trs_n = ' '.join(f"{int(x)}" for x in traces_number)f.write('traces number: ' + trs_n + '\n')success_rate = ' '.join(f"{x:.2f}" for x in success_rate)f.write('success rate: ' + success_rate + '\n')guess_entropy = ' '.join(f"{x:.2f}" for x in guess_entropy)f.write('guess entropy: ' + guess_entropy + '\n')f.write(f'tips: When guess entropy equals 10, 1, or 0, the corresponding traces number are {guess_entropy_10}, {guess_entropy_1}, and {guess_entropy_0}.\n')f.write(f'tips: When success rate equals 0.9, 0.99, or 1, the corresponding traces number are {success_rate_p9}, {success_rate_p99}, and {success_rate_1}.\n\n')def stratified_sampling(X, y, sample_n):"""分層抽樣函數,確保每個類別的樣本比例均衡參數:X: 特征數據y: 標簽數據sample_n: 總樣本數返回:X_resampled: 重采樣后的特征數據y_resampled: 重采樣后的標簽數據"""unique_classes, class_counts = np.unique(y, return_counts=True)n_classes = len(unique_classes)base_samples_per_class = sample_n // n_classesremainder = sample_n % n_classesbalanced_samples = []for i, cls in enumerate(unique_classes):target_sample_index = np.where(y == cls)[0]n_samples = class_counts[i]if i < remainder:required = base_samples_per_class + 1else:required = base_samples_per_classif required > n_samples:repeats = required // n_samplesremainder_samples = required % n_samplessamples = np.concatenate([np.tile(target_sample_index, repeats),np.random.choice(target_sample_index, remainder_samples, replace=False)])else:samples = np.random.choice(target_sample_index, required, replace=(required > n_samples))balanced_samples.append(samples)balanced_samples = np.concatenate(balanced_samples)np.random.shuffle(balanced_samples)X_resampled = X[balanced_samples]y_resampled = y[balanced_samples]return X_resampled, y_resampleddef show_result():"""可視化攻擊結果從result.txt文件中讀取數據并繪制成功率與猜測熵曲線"""result_path = 'result.txt'# 讀取結果文件with open(result_path, 'r', encoding='utf-8') as f:lines = f.readlines()# 初始化數據列表traces = []success_rate = []guess_entropy = []# 解析結果文件for line in lines:if line.startswith('traces number:'):traces = list(map(int, line.split(':')[1].strip().split()))elif line.startswith('success rate:'):success_rate = list(map(float, line.split(':')[1].strip().split()))elif line.startswith('guess entropy:'):guess_entropy = list(map(float, line.split(':')[1].strip().split()))# 檢查是否成功讀取數據if not traces or not success_rate or not guess_entropy:print("Error: Could not find valid data in result file.")return# 創建圖形和雙軸fig, ax1 = plt.subplots(figsize=(12, 6))# 繪制成功率曲線(左軸,藍色)color = 'tab:blue'ax1.set_xlabel('Number of Traces', fontsize=12)ax1.set_ylabel('Success Rate', color=color, fontsize=12)ax1.plot(traces, success_rate, color=color, label='Success Rate', linewidth=2)ax1.tick_params(axis='y', labelcolor=color)ax1.grid(True, linestyle='--', alpha=0.7)ax1.set_ylim(0, 1.05) # 設置成功率y軸范圍# 創建第二個y軸用于猜測熵ax2 = ax1.twinx()color = 'tab:red'ax2.set_ylabel('Guess Entropy', color=color, fontsize=12)ax2.plot(traces, guess_entropy, color=color, label='Guess Entropy', linewidth=2)ax2.tick_params(axis='y', labelcolor=color)ax2.set_ylim(0, 256) # 固定猜測熵y軸范圍為0-256# 添加圖例lines1, labels1 = ax1.get_legend_handles_labels()lines2, labels2 = ax2.get_legend_handles_labels()ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper right', fontsize=10)# 添加標題plt.title('MLSCA (SVC) Attack Performance: Success Rate and Guess Entropy', fontsize=14, pad=20)# 調整布局并保存圖像(同時保存svg格式和png格式)fig.tight_layout()plt.savefig('result.svg', format='svg', bbox_inches='tight')plt.savefig('result.png', dpi=600, bbox_inches='tight')plt.show()def test():"""測試函數:執行完整的MLSCA流程"""target_byte = 0 # 攻擊的字節位置(0表示第一個字節)traces_train_n = 50000 # 用于訓練模型的曲線數量sample_n = 10000 # 實際用于訓練的樣本數量(經過分層抽樣后)# 主密鑰(16字節)mkey = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10]# 加載能量曲線數據trs_path = 'AES_POWER_STM32F_NO-PROTECT_60000.trs'trs_dict = load_trs_to_dict(trs_path)traces, plaintext = trs_dict['TRACES'], trs_dict['PARAM']['plaintext']# 生成真實中間值向量和假設中間值矩陣actual, hypo = get_intermediate_values(plaintext, target_byte, mkey[target_byte])# 分割數據集traces_build, traces_match = traces[:traces_train_n], traces[traces_train_n:]actual_build, hypo_match = actual[:traces_train_n], hypo[traces_train_n:]# 對訓練數據進行分層抽樣,平衡各類樣本數量traces_build, actual_build = stratified_sampling(traces_build, actual_build, sample_n)# 訓練SVC模型model = train_model(traces_build, actual_build)# 設置實驗參數repeat_n = 100 # 重復次數sample = 100 # 每次實驗使用的能量曲線數量step = 1 # 能量曲線數量增加的步長# 執行猜測熵分析guess_entropy(traces_match, hypo_match, model, mkey[target_byte], repeat_n=repeat_n, sample=sample, step=step)# 可視化結果show_result()if __name__ == '__main__':test()
?三、實驗結果
實驗使用STM32F系列芯片采集的60,000條AES加密功耗曲線,其中50,000條用于模型訓練,剩余10,000條用于攻擊。結果顯示,經過分層抽樣的SVC模型表現出色,僅需約10條能量曲線即可將猜測熵降至10以下,約25條曲線能使成功率超過90%。當使用51條曲線時,猜測熵接近0,成功率穩定在100%,表明密鑰被完全恢復。
結果可視化展示了猜測熵和成功率隨能量曲線數量增加的變化趨勢。兩條曲線呈現明顯的負相關關系,符合理論預期。值得注意的是,在曲線數量較少時,指標改善速度較快,隨后趨于平緩,這種非線性特性反映了機器學習模型的特征學習過程。實驗結果驗證了MLSCA方法對未加防護AES實現的有效性,也為評估防護措施的安全性提供了基準參考。
四、總結?
本文研究了基于機器學習(SVC)的側信道分析方法(MLSCA)在AES算法實現上的應用。通過采集STM32F芯片的功耗曲線,以S盒輸出的漢明重量作為泄漏模型,構建支持向量機分類器建立功耗特征與密鑰信息的映射關系。實驗采用分層抽樣優化數據分布,使用50,000條曲線訓練模型,10,000條曲線測試攻擊效果。結果表明,該方法僅需約25條功耗曲線即可實現90%以上的密鑰恢復成功率,51條曲線時猜測熵趨近于0,完全破解密鑰。研究驗證了MLSCA對未防護AES實現的高效攻擊能力,揭示了硬件密碼實現中側信道防護的重要性。?