在AI業務的開發的過程當中,咱們經常須要對模型文件進行加密。
咱們從如下幾個方面來講一說AES的加密原理以及AOE裏的工程實踐。git
常見的加密算法,主要分爲兩種:
對稱加密,採用單密鑰的加密方法,同一個密鑰能夠同時用來加密和解密。經常使用的對稱加密算法有DES,3DES,AES等。
非對稱加密,須要兩個密鑰來進行加密和解密,這兩個密鑰是公開密鑰(public key,簡稱公鑰)和私有密鑰(private key,簡稱私鑰)。經常使用的非對稱加密算法有RSA,Elgamal,ECC等。github
用個比喻來理解一下這2種不一樣的加密方式:
對稱加密:假設有一個密碼箱,A設置了一個密碼鎖好箱子,B知道這個密碼之後,輸入這個密碼就能夠打開箱子,這個密碼就是祕鑰,A和B使用相同的密碼。
非對稱加密:有一把鎖和一把惟一的鑰匙,A用鎖把箱子鎖好,B只有用這把鑰匙才能打開這個箱子。這把鎖就能夠理解爲公鑰,是公開的。這把鑰匙能夠理解爲私鑰,是不公開的,是不在網絡裏傳輸的,只有私鑰的擁有者才能打開這個箱子。算法
簡單比較一下他們的差別:安全
功能特性 | 對稱加密 | 非對稱加密 |
---|---|---|
密鑰特徵 | 加密方和解密方使用同一個密鑰 | 加密方和解密方使用不一樣的密鑰 |
加密效率 | 效率高,速度快 | 速度慢 |
密鑰安全性 | 傳輸過程當中容易泄漏 | 安全性高 |
在實際的應用中,通常都是對稱加密和非對稱加密算法相結合,這樣既能夠保證加密的速度,也能夠保證祕鑰傳輸的安全性。以AES和RSA結合爲例,有一種通用的作法,在向服務器發送一段數據信息的時候,先用AES算法對數據進行加密,再使用服務器的RSA公鑰對AES的密鑰進行加密後,最後數據和加密後的AES KEY上傳到服務器。這樣只有知道了服務器RSA私鑰,才能解開獲得正確的AES KEY,最終用AES KEY才能解開這段密文。服務器
深度學習中,模型訓練是最關鍵和最有挑戰性的。不只須要有豐富的經驗定義出合適的模型,還須要有大量的數據集,常常須要3-4天才能訓練出一個模型。出於對知識產權的保護或者商業保護,咱們經常須要對訓練出來的模型文件進行加密。思考一下咱們對模型加密的需求:速度要快,安全性要高,不依賴服務器的狀況下本地也能加密解密。另外還須要有一個摘要算法來校驗,保證模型的完整性。咱們初步考慮使用AES算法,摘要算法使用MD5。下面咱們來看看AES算法是如何工做的。微信
AES,Advanced Encryption Standard,是對稱加解密算法的最經典算法之一,標準的AES加密位寬是每一個塊128bit,密鑰分爲128bit,192bit,256bit,這就是一般說的AES-128,AES-192,AES-256。咱們從幾個方面來具體瞭解一下AES加密。網絡
AES算法主要有四種操做處理,分別是輪密鑰加層(Add Round Key)、字節代換層(SubBytes)、行位移層(Shift Rows)、列混淆層(Mix Columns)。AES加密的過程,並非明文和密鑰簡單運算一下。以AES128爲例,它要執行10輪加密。實際的執行過程是這樣的:函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static
void
Cipher(state_t* state,
const
uint8_t* RoundKey)
{
uint8_t round = 0;
// Add the First round key
AddRoundKey(0, state, RoundKey);
// There will be Nr rounds.
for
(round = 1; round < Nr; ++round)
{
SubBytes(state);
ShiftRows(state);
MixColumns(state);
AddRoundKey(round, state, RoundKey);
}
// The last round is given below.
SubBytes(state);
ShiftRows(state);
AddRoundKey(Nr, state, RoundKey);
}
|
SubBytes,字節混淆。AES處理的最小單位是8位無符號整數,正好能夠對應伽羅瓦域GF(2^8)上的一個元素,混淆的方法是先計算每個字節在GF(2^8)的乘法逆元,目的是提供非線性變換。再對結果作一次仿射變換,目的是改變掉伽羅瓦域的結構。工具
由於GF(2^8)中的每個元素,均可以用多項式來表示:a7x^7 + a6x^6 + a5x^5 + ... + a1x + a0,其中a7-a0,只能從GF(2)中取值。因此對某個字節計算逆元,能夠轉換成求多項式的逆元。以0xac爲例,寫成二進制是10101100,寫成多項式就是x^7 + x^5 + x^3 + x^2,計算逆元就變成計算這個多項式的逆元。獲得多項式逆元之後,咱們能夠把它轉換成16進制的值。仿射變換簡單理解,就是經過某個計算,獲得一個新的值。具體的計算方法,咱們就很少介紹了。實際中,能夠用查表法來完成這一步驟。性能
由於這些步驟獲得的每一個字節的值是固定對,因此咱們能夠計算出GF(2^8)中每一個元素對應的值,最後咱們能夠獲得了一張16*16的查找表,叫作Substitution-box,簡稱sbox或者s盒。對於每一個輸入的字節,例如輸入字節的十六進制形式位0xAB,則在表格的縱座標中定位A,再在縱座標中定位B,最後使用s(a,b)的值來替換這個字節的值。經過這個步驟,咱們的輸入數據D1-D16,變成了S1-S16。
將16個S盒變換後的字節,從上往下、從左到右地寫成了一個矩陣。第一行保持不動,第二行向左移動1格,第三行向左移動2格,第四行向左滑移動3格,如圖所示:
用下面這個矩陣和ShiftRows以後的結果相乘。列混淆操做混淆了輸入矩陣的每一列,使輸入的每一個字節都會影響到4個輸出字節。這部分包含了矩陣乘法,伽羅瓦域內加法和乘法的相關知識。總的來講,ShiftRows和MixColumns兩步驟爲這個密碼系統提供了擴散性。
左邊矩陣中的01,02,03對應的多項式分別是1,x,x+1。假設右邊矩陣輸入所有是0x11,對應的多項式是x^4+1。使用GF(2^8)中的加法和乘法,計算過程以下:
1
2
3
4
|
C(i) = (01 + 01 + 02 + 03) * 11
=(1 + 1 + x + x + 1) * (x^4 + 1)
= 1 * (x^4+1)
= x^4 + 1
|
乘法結果有兩種狀況:
在上面的幾個步驟中,咱們是對輸入對數據進行混淆。AddRoundKey每執行一次叫作一輪加密,這一步會執行屢次。簡單來講就是把密鑰和混淆後的結果進行xor運算,但在每一輪使用的密鑰都是根據上一輪的密鑰變換而來的。
輪密鑰是如何生成的?以AES-128爲例,密鑰一共16個字節,咱們把它們4字節分紅一組,計做W0-W3。在每一輪中,經過函數g變換和固定的公式,能夠獲得這一輪的密鑰。一共生成44個W(i),每4個爲一組一共生成11個密鑰k0-k10,其中k0是原始密鑰。輪數依賴於密鑰長度,16字節密鑰對應10輪,24字節密鑰對應12輪,32字節對應14輪。具體的計算過程咱們就再也不描述了,看起來簡單,實際上背後有大量的數學知識和研究做爲支撐。
對於解密,就是把加密的過程反過來,以下面的參考代碼,其中的InvShiftRows,InvSubBytes,InvMixColumns分別是對應算法的逆運算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static
void
InvCipher(state_t* state,
const
uint8_t* RoundKey)
{
uint8_t round = 0;
// Add the First round key
AddRoundKey(Nr, state, RoundKey);
// There will be Nr rounds.
for
(round = (Nr - 1); round > 0; --round)
{
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(round, state, RoundKey);
InvMixColumns(state);
}
// The last round is given below.
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(0, state, RoundKey);
}
|
日常工做中,咱們是不推薦本身手寫這類成熟專業的加密算法的,很容易寫出漏洞或者錯誤,最終形成嚴重的安全問題。
AES只能對固定長度的數據進行加密,對於不定長的數據,咱們須要把它切分紅若干定長的數據,再進行加密解密,這就是咱們常說的分組加密。分組加密有ECB,CBC,CFB,OFB這幾種加密模式,咱們介紹一下ECB模式和CBC模式,以及Padding的對加密解密的影響。
ECB模式
ECB模式又稱電子密碼本模式(Electronic codebook):ECB是最簡單的塊密碼加密模式,加密前根據加密塊大小(如AES爲128位)分紅若干塊,以後將每塊使用相同的密鑰單獨加密,解密同理。具體見下圖:
(圖片來自維基百科)
ECB模式因爲每塊數據的加密是獨立的,因此能夠分塊進行並行加密或者解密。它的缺點是相同的明文塊會被加密成相同的密文塊,因此這種方法在某些條件下安全性相對不是很高。
CBC模式
CBC模式又稱密碼分組連接(Cipher-block chaining):CBC模式對於每一個待加密的密碼塊在加密前會先與前一個密碼塊的密文異或而後再用加密器加密,第一個明文塊與一個叫初始化向量IV的數據塊異或。具體見下圖:
(圖片來自維基百科)
完成加密或解密後會更新初始化向量IV,CBC模式安全性更高,但因爲對每一個數據塊的加密依賴前一個數據塊的加密,因此加密是沒法並行的。
用一張圖來比較看一下ECB和CBC加密的效果,能夠發現使用CBC模式分散性和安全性更好。
(圖片來自網絡)
顯然,不一樣的padding對加密與解密是有影響的,因此在加密和解密的時候須要保證padding的方式是一致的。
理論上大部分的算法都是能夠破解的,只是有可能須要很長時間的計算才能破解。AES加密算法在目前的計算能力下,直接破解幾乎是不可能的。從咱們保護模型的目的來看,使用AES-128就能夠知足咱們的需求。另外,在AES算法被標準化後,不少硬件芯片和軟件工具都實現了對AES的支持,使用AES算法來加密解密,性能也很是高。綜合考慮了性能和安全性,咱們使用了AES-128/CBC算法來作加密,並在這個基礎上結合MD5摘要算法,對文件的完整性作校驗。
咱們從如下幾個方面來看看AOE的加密組件是如何實現的:
AOE的加密文件結構
咱們設計了一個新的文件結構,加密後的文件結構以下:
1byte | 4bytes | 16bytes | nbytes |
---|---|---|---|
Version Code | File Length | File MD5 | 加密後的模型文件數據 |
咱們在加密後的模型數據前,增長了一個head,head一共21個byte:
AOE初版的加密算法(Version Code爲1),加密和解密均可以在本地完成,不須要和服務器進行交互,對應的加密解密過程以下:
AOE的加密過程
1,採用AES-128/CBC/No Padding對模型加密。
2,給文件加上21字節的文件頭,Version Code + File Length + File MD5。
3,使用文件MD5和加密後的模型作簡單的Swap操做,把MD5的16個byte,分別和模型加密後的前16k數據的第一個字節進行交換。
使用AES方式加密,咱們面臨一個問題,密鑰很容易泄漏。爲了解決這個問題,首先咱們選擇了CBC模式,其次咱們對加密後對文件作了一下混淆。這樣即便別人知道了咱們的AES KEY,若是不知道咱們加密組件裏的混淆方式,由於是CBC模式加密,因此他也是沒法解開咱們加密後的文件的。
AOE的解密過程
解密的過程和加密的過程是相反的,具體的算法以下:
1,讀取加密文件的前21個字節,獲得Version Code,文件長度。
2,讀取加密數據的前16k數據的第一個字節,和head裏的MD5字段進行swap,通過這一步之後,能夠獲得文件的MD5和原始的加密後的數據。
3,採用AES-128/CBC/No Padding對模型解密,獲得解密文件之後使用文件MD5來檢驗文件的完整性。
AOE加密組件的使用
AOE加密組件,提供了C版本和JNI封裝,JAVA版本和Python版本,在端上咱們更推薦使用C版本,在服務器後臺咱們推薦使用JAVA版本或者Python版原本作一些批量的工做。咱們提供瞭解密到內存和文件兩種方式,咱們更推薦直接解密到內存裏,這樣不會生成臨時文件,安全性更高。
密碼學對大部分人來講是很是專業的,須要大量的數學知識,加密和破解也一直是矛和盾的關係。目前AOE SDK站在成熟算法的肩膀上,結合了AES算法對模型進行了加密,後續咱們還會擴展一些新的加密算法,給你們參考和使用。歡迎你們來使用和提建議。
github.com/didi/aoe
(AoE (AI on Edge,終端智能,邊緣計算) 是一個終端側AI集成運行時環境 (IRE),幫助開發者提高效率)
歡迎添加小助手微信進入AOE開源交流羣!