iOS開發-AES加密

上次的文章中對常用的加密算法進行了一些簡單的介紹,這次我們就挑一個出來說說,今天的主角的是對稱加密中的當頭大哥AES加密。

AES加密簡介

AES全稱Advanced Encryption Standard,中文名稱叫高級加密標準,在密碼學中被叫做Rijndael加密法,這個標準已經替代原來的DES,成為美國政府采用的一種區塊加密標準。微信小程序中的加密傳輸就是使用的AES加密算法。

aes1.png

根據上圖,解釋一下各個部分的作用:

明文P:沒有經過加密的數據。

密鑰K:用來加密明文的密碼,在對稱加密中,加密和解密的密鑰是相同的,所以密鑰要保證安全,如果一旦密鑰泄漏了,那么數據就基本上不存在安全性了。

AES加密函數:設AES加密函數為E,則C = E(K,P),其中K為密鑰,C為密文。所以通過加密函數E,可以把明文+密鑰生成密文。

密文C:經過加密處理后的數據。

AES解密函數:如加密函數一樣,設AES解密函數為D,則P = D(C,P),也就是說,可以通過解密函數D,將密文+密鑰生成明文。

AES的基本結構

AES為分組密碼,分組密碼就是把明文分成一組一組的,每組的長度相等,每次加密一組數據,一直到整個明文被加密完畢。

在AES標準規范中,分組長度只能是128位,也就是說每個分組為16個字節(每個字節8位)。密鑰的長度可以使用128位、192位或256位。密鑰的長度不同,推薦加密的輪數也不同。一般加密的輪數如下:

AES | 密鑰長度(32位比特字) | 分組長度(32位比特字) | 加密輪數

--- | --- | --- | ---

AES-128 | 4 | 4 | 10

AES-192 | 6 | 4 | 12

AES-256 | 8 | 4 | 14

這個加密輪數是什么意思呢?

就比如說上邊的加密公式C = E(K,P),如果加密10輪,就是需要執行10次這個函數,這個輪函數的前9次都是相同的,只有第10次時有所不同。

AES處理的單位是字節,128位的輸入明文分組P和輸入密鑰K都被分為16個字節,分別記為P = P0 P1 ... P15 和 K = K0 K1 ... K15,如明文分組P = abcdefghijklmnop,其中的字符a對應P0,p對應P15。一般地,明文分組用字節為單位的正方形矩陣 描述,成為狀態矩陣。在算法的每一輪中,狀態矩陣的內容不斷發生變化,最后的結果作為密文輸出。該矩陣的排列順序為從上到下、從左至右依次排列,如圖所示:

aes2.png

現在假設明文分組P = abcdefghijklmnop,則對應的矩陣圖為:

aes3.png

圖中使用十六進制表示,即使用0x61表示a,可以看到,經過AES加密之后,得到的結果已經看不出原來的樣子。

同樣的,128位密鑰也是用字節為單位的矩陣表示,矩陣的每一列被成為1個32位比特字。通過密鑰編排函數該密鑰矩陣被擴展成一個44個字組成的序列W[0],w[1],...,w[43],該序列的前4個元素W[0],W[1],W[2],W[3]是原始密鑰,用于加密運算中的初始密鑰,后邊40個字分為10組,每組4個字(128比特)分別用于10輪加密運算中的輪密鑰加,如下圖:

aes4.png

上圖中,設K = “abcdefghijklmnop”,則K0 = a, K15 = p, W[0] = K0 K1 K2 K3 = “abcd”。

AES的整體結構如下圖所示,其中的W[0,3]是指W[0]、W[1]、W[2]和W[3]串聯組成的128位密鑰。加密的第1輪到第9輪的輪函數一樣,包括4個操作:字節代換、行位移、列混合和輪密鑰加。最后一輪迭代不執行列混合。另外,在第一輪迭代之前,先將明文和原始密鑰進行一次異或加密操作。

aes5.png

上圖為AES加密過程和解密過程,解密過程也為10輪,每一輪操作都是之前加密操作的逆操作。由于AES的4個輪操作都是可逆的,因此,解密的操作的第一輪就是順序執行逆位移位,逆字節代換,輪密鑰加和逆列混合。

AES*算法流程

AES加密算法主要有4個操作:

  • 字節替代(SubBytes)

  • 行位移(ShiftRows)

  • 列混淆(MixColumns)

  • 輪密鑰加(AddRoundKey)

通過之前的介紹,我們也可以了解到,加密和解密是一個相互可逆的過程。而且所有的順序剛好是相反的。

字節替代

AES的字節替代實際上就是一個簡單的查表操作,AES定義了一個S盒和一個逆S盒。

正向字節替代

S盒 :

行/列 0 1 2 3 4 5 6 7 8 9 A B C D E F
0 0x63 0x7c 0x77 0x7b 0xf2 0x6b 0x6f 0xc5 0x30 0x01 0x67 0x2b 0xfe 0xd7 0xab 0x76
1 0xca 0x82 0xc9 0x7d 0xfa 0x59 0x47 0xf0 0xad 0xd4 0xa2 0xaf 0x9c 0xa4 0x72 0xc0
2 0xb7 0xfd 0x93 0x26 0x36 0x3f 0xf7 0xcc 0x34 0xa5 0xe5 0xf1 0x71 0xd8 0x31 0x15
3 0x04 0xc7 0x23 0xc3 0x18 0x96 0x05 0x9a 0x07 0x12 0x80 0xe2 0xeb 0x27 0xb2 0x75
4 0x09 0x83 0x2c 0x1a 0x1b 0x6e 0x5a 0xa0 0x52 0x3b 0xd6
0xb3 0x29 0xe3 0x2f 0x84
5 0x53 0xd1 0x00 0xed 0x20 0xfc 0xb1 0x5b 0x6a 0xcb 0xbe 0x39 0x4a 0x4c 0x58 0xcf
6 0xd0 0xef 0xaa 0xfb 0x43 0x4d 0x33 0x85 0x45 0xf9 0x02 0x7f 0x50 0x3c 0x9f 0xa8
7 0x51 0xa3 0x40 0x8f 0x92 0x9d 0x38 0xf5 0xbc 0xb6 0xda 0x21 0x10 0xff 0xf3 0xd2
8 0xcd 0x0c 0x13 0xec 0x5f 0x97 0x44 0x17 0xc4 0xa7 0x7e 0x3d 0x64 0x5d 0x19 0x73
9 0x60 0x81 0x4f 0xdc 0x22 0x2a 0x90 0x88 0x46 0xee 0xb8 0x14 0xde 0x5e 0x0b 0xdb
A 0xe0 0x32 0x3a 0x0a 0x49 0x06 0x24 0x5c 0xc2 0xd3 0xac 0x62 0x91 0x95 0xe4 0x79
B 0xe7 0xc8 0x37 0x6d 0x8d 0xd5 0x4e 0xa9 0x6c 0x56 0xf4 0xea 0x65 0x7a 0xae 0x08
C 0xba 0x78 0x25 0x2e 0x1c 0xa6 0xb4 0xc6 0xe8 0xdd 0x74 0x1f 0x4b 0xbd 0x8b 0x8a
D 0x70 0x3e 0xb5 0x6 0x48 0x03 0xf6 0x0e 0x61 0x35 0x57 0xb9 0x86 0xc1 0x1d 0x9e
E 0xe1 0xf8 0x98 0x11 0x69 0xd9 0x8e 0x94 0x9b 0x1e 0x87 0xe9 0xce 0x55 0x28 0xdf
F 0x8c 0xa1 0x89 0x0d 0xbf 0xe6 0x42 0x68 0x41 0x99 0x2d 0x0f 0xb0 0x54 0xbb 0x16

狀態矩陣中的元素按照下面的方式映射為一個新的字節:把該字節的高4位作為行值,低4位作為列值,取出S盒或者逆S盒中對應的行的元素作為輸出。例如,加密時,輸出的字節S1為0x12,則查S盒的第0x01行和0x02列,得到值0xc9,然后替換S1原有的0x12為0xc9。狀態矩陣經字節代換后的圖如下:

aes6.png

逆向字節替代

逆向字節替代也就是查詢逆S盒來變換,逆S盒如下:

行/列 0 1 2 3 4 5 6 7 8 9 A B C D E F
0 0x52 0x09 0x6a 0xd5 0x30 0x36 0xa5 0x38 0xbf 0x40 0xa3 0x9e 0x81 0xf3 0xd7 0xfb
1 0x7c 0xe3 0x39 0x82 0x9b 0x2f 0xff 0x87 0x34 0x8e 0x43 0x44 0xc4 0xde 0xe9 0xcb
2 0x54 0x7b 0x94 0x32 0xa6 0xc2 0x23 0x3d 0xee 0x4c 0x95 0x0b 0x42 0xfa 0xc3 0x4e
3 0x08 0x2e 0xa1 0x66 0x28 0xd9 0x24 0xb2 0x76 0x5b 0xa2 0x49 0x6d 0x8b 0xd1 0x25
4 0x72 0xf8 0xf6 0x64 0x86 0x68 0x98 0x16 0xd4 0xa4 0x5c 0xcc 0x5d 0x65 0xb6 0x92
5 0x6c 0x70 0x48 0x50 0xfd 0xed 0xb9 0xda 0x5e 0x15 0x46 0x57 0xa7 0x8d 0x9d 0x84
6 0x90 0xd8 0xab 0x00 0x8c 0xbc 0xd3 0x0a 0xf7 0xe4 0x58 0x05 0xb8 0xb3 0x45 0x06
7 0xd0 0x2c 0x1e 0x8f 0xca 0x3f 0x0f 0x02 0xc1 0xaf 0xbd 0x03 0x01 0x13 0x8a 0x6b
8 0x3a 0x91 0x11 0x41 0x4f 0x67 0xdc 0xea 0x97 0xf2 0xcf 0xce 0xf0 0xb4 0xe6 0x73
9 0x96 0xac 0x74 0x22 0xe7 0xad 0x35 0x85 0xe2 0xf9 0x37 0xe8 0x1c 0x75 0xdf 0x6e
A 0x47 0xf1 0x1a 0x71 0x1d 0x29 0xc5 0x89 0x6f 0xb7 0x62 0x0e 0xaa 0x18 0xbe 0x1b
B 0xfc 0x56 0x3e 0x4b 0xc6 0xd2 0x79 0x20 0x9a 0xdb 0xc0 0xfe 0x78 0xcd 0x5a 0xf4
C 0x1f 0xdd 0xa8 0x33 0x88 0x07 0xc7 0x31 0xb1 0x12 0x10 0x59 0x27 0x80 0xec 0x5f
D 0x60 0x51 0x7f 0xa9 0x19 0xb5 0x4a 0x0d 0x2d 0xe5 0x7a 0x9f 0x93 0xc9 0x9c 0xef
E 0xa0 0xe0 0x3b 0x4d 0xae 0x2a 0xf5 0xb0 0xc8 0xeb 0xbb 0x3c 0x83 0x53 0x99 0x61
F 0x17 0x2b 0x04 0x7e 0xba 0x77 0xd6 0x26 0xe1 0x69 0x14 0x63 0x55 0x21 0x0c 0x7d

行位移

正向行位移

行移位是一個簡單的左循環移位操作。當密鑰長度為128比特時,狀態矩陣的第0行左移0字節,第1行左移1字節,第2行左移2字節,第3行左移3字節,如下圖所示:

aes7.png

逆向行位移

行移位的逆變換是將狀態矩陣中的每一行執行相反的移位操作,例如AES-128中,狀態矩陣的第0行右移0字節,第1行右移1字節,第2行右移2字節,第3行右移3字節。

列混淆

正向列混淆

列混合變換是通過矩陣相乘來實現的,經行移位后的狀態矩陣與固定的矩陣相乘,得到混淆后的狀態矩陣,如下圖的公式所示:

aes8.png

狀態矩陣中的第j列(0 ≤j≤3)的列混合可以表示為下圖所示:

aes9.png

其中,矩陣元素的乘法和加法都是定義在基于GF(2^8)上的二元運算,并不是通常意義上的乘法和加法。這里涉及到一些信息安全上的數學知識,不過不懂這些知識也行。其實這種二元運算的加法等價于兩個字節的異或,乘法則復雜一點。對于一個8位的二進制數來說,使用域上的乘法乘以(00000010)等價于左移1位(低位補0)后,再根據情況同(00011011)進行異或運算,設S1 = (a7 a6 a5 a4 a3 a2 a1 a0),剛0x02 * S1如下圖所示:

aes10.png

也就是說,如果a7為1,則進行異或運算,否則不進行。

類似地,乘以(00000100)可以拆分成兩次乘以(00000010)的運算:

aes11.png

乘以(0000 0011)可以拆分成先分別乘以(0000 0001)和(0000 0010),再將兩個乘積異或:

aes12.png

逆向列混淆

逆向列混合變換可由下圖的矩陣乘法定義:

aes13.png

可以驗證,逆變換矩陣同正變換矩陣的乘積恰好為單位矩陣。

輪密鑰加

輪密鑰加是將128位輪密鑰Ki同狀態矩陣中的數據進行逐位異或操作,如下圖所示。其中,密鑰Ki中每個字W[4i],W[4i+1],W[4i+2],W[4i+3]為32位比特字,包含4個字節,他們的生成算法下面在下面介紹。輪密鑰加過程可以看成是字逐位異或的結果,也可以看成字節級別或者位級別的操作。也就是說,可以看成S0 S1 S2 S3 組成的32位字與W[4i]的異或運算。

aes14.png

輪密鑰加的逆運算同正向的輪密鑰加運算完全一致,這是因為異或的逆操作是其自身。輪密鑰加非常簡單,但卻能夠影響S數組中的每一位。

iOS中的*AES

在iOS中使用AES進行加密解密的一個關鍵的函數就是:


CCCryptorStatus CCCrypt(

 CCOperation op,  /* kCCEncrypt, etc. */

 CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */

 CCOptions options, /* kCCOptionPKCS7Padding, etc. */

 const void *key,

 size_t keyLength,

 const void *iv,  /* optional initialization vector */

 const void *dataIn,  /* optional per op and alg */

 size_t dataInLength,

 void *dataOut, /* data RETURNED here */

 size_t dataOutAvailable,

 size_t *dataOutMoved)

 __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);

我們首先來介紹一下這么一大堆參數都是什么用:

  • CCOperation op:用來代表加密或者解密,kCCEncrypt = 加密,kCCDecrypt = 解密;

  • CCAlgorithm alg:用來代表加密算法,有kCCAlgorithmAES128...具體的可以cmd+ctl點擊進去看;

  • CCOptions options:填充模式,iOS中只提供了kCCOptionPKCS7Padding和kCCOptionECBMode兩種,這個在于后臺和安卓交互時要注意一點。

  • const void *key:密鑰長度,一般使用char keyPtr[kCCKeySizeAES256+1];

  • const void *dataIn:加密信息的比特數

  • size_t dataInLength:加密信息的長度

  • void *dataOut:用來輸出加密結果

  • size_t dataOutAvailable:輸出的大小

了解了這個函數大概的工作方式之后,我們就可以開始擼起袖子寫代碼了。

這里我們主要對NSData和NSString進行加密解密。其實實際上NSString也是轉換為了NSData,然后再進行加密解密的。

引入框架

第一步,重中之重的一步就是引入框架了


#import <CommonCrypto/CommonCrypto.h>

#import <CommonCrypto/CommonDigest.h>

NSData 加密

首先我們先寫一下對NSData的加密,因為之前介紹過了主要的函數,這里就直接上代碼了。


//對NSData 進行加密

- (NSData *)encryptDataWithData:(NSData *)data Key:(NSString *)key

{

 char keyPtr[kCCKeySizeAES128 + 1];

 bzero(keyPtr, sizeof(keyPtr));

 [key getCString:keyPtr maxLength:sizeof(key) encoding:NSUTF8StringEncoding];

 NSUInteger dataLength = [data length];

 size_t bufferSize = dataLength + kCCBlockSizeAES128;

 void *buffer = malloc(bufferSize);

 size_t numBytesEncrypted = 0;

 CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,

 kCCAlgorithmAES128,

 kCCOptionPKCS7Padding | kCCOptionECBMode,

 keyPtr,

 kCCBlockSizeAES128,

 NULL,

 [data bytes],

 dataLength,

 buffer,

 bufferSize,

 &numBytesEncrypted);

 if(cryptStatus == kCCSuccess)

 {

 return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];

 }

 free(buffer);

 return nil;

}

NSData 解密


// 解密

- (NSData *)decryptDataWithData:(NSData *)data andKey:(NSString *)key

{

 char keyPtr[kCCKeySizeAES128 + 1];

 bzero(keyPtr, sizeof(keyPtr));

 [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

 NSUInteger dataLength = [data length];

 size_t bufferSize = dataLength + kCCBlockSizeAES128;

 void *buffer = malloc(bufferSize);

 size_t numBytesDecrypted = 0;

 CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCBlockSizeAES128, NULL, [data bytes], dataLength, buffer, bufferSize, &numBytesDecrypted);

 if(cryptStatus == kCCSuccess)

 {

 return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];

 }

 free(buffer);

 return nil;

}

NSString 加密

對NSString加密的時候需要注意一點,當計算中文字符串長度時,需要使用char字符長度計算,而不要使用[NSString length]計算。


- (NSString *)encryptStringWithString:(NSString *)string andKey:(NSString *)key

{

 const char *cStr = [string cStringUsingEncoding:NSUTF8StringEncoding];

 NSData *data = [NSData dataWithBytes:cStr length:[string length]];

 //對數據進行加密

 NSData *result = [self encryptDataWithData:data Key:key];

 //轉換為2進制字符串

 if(result && result.length > 0)

 {

 Byte *datas = (Byte *)[result bytes];

 NSMutableString *outPut = [NSMutableString stringWithCapacity:result.length];

 for(int i = 0 ; i < result.length ; i++)

 {

 [outPut appendFormat:@"%02x",datas[i]];

 }

 return outPut;

 }

 return nil;

}

NSString 解密


- (NSString *)decryptStringWithString:(NSString *)string andKey:(NSString *)key

{

 NSMutableData *data = [NSMutableData dataWithCapacity:string.length/2.0];

 unsigned char whole_bytes;

 char byte_chars[3] = {'\0','\0','\0'};

 int i;

 for(i = 0 ; i < [string length]/2 ; i++)

 {

 byte_chars[0] = [string characterAtIndex:i * 2];

 byte_chars[1] = [string characterAtIndex:i * 2 + 1];

 whole_bytes = strtol(byte_chars, NULL, 16);

 [data appendBytes:&whole_bytes length:1];

 }

 NSData *result = [self decryptDataWithData:data andKey:key];

 if(result && result.length > 0)

 {

 return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];

 }

 return nil;

}

結束

以上就是AES加密的簡單原理以及iOS中對AES加密解密的簡單使用,其中的圖片和大部分內容都是來自一位大神博客中的內容。

本文章也僅限個人學習使用,如果有哪里不對的地方請大佬們多多指正。

參考文檔

AES加密算法的詳細介紹與實現

密碼算法詳解——AES

iOS開發_AES加密和解密算法的實現

iOS開發之Objective-c的AES加密和解密算法的實現

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容