上次的文章中對常用的加密算法進行了一些簡單的介紹,這次我們就挑一個出來說說,今天的主角的是對稱加密中的當頭大哥AES加密。
AES加密簡介
AES全稱Advanced Encryption Standard,中文名稱叫高級加密標準,在密碼學中被叫做Rijndael加密法,這個標準已經替代原來的DES,成為美國政府采用的一種區塊加密標準。微信小程序中的加密傳輸就是使用的AES加密算法。
根據上圖,解釋一下各個部分的作用:
明文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。一般地,明文分組用字節為單位的正方形矩陣 描述,成為狀態矩陣。在算法的每一輪中,狀態矩陣的內容不斷發生變化,最后的結果作為密文輸出。該矩陣的排列順序為從上到下、從左至右依次排列,如圖所示:
現在假設明文分組P = abcdefghijklmnop,則對應的矩陣圖為:
圖中使用十六進制表示,即使用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輪加密運算中的輪密鑰加,如下圖:
上圖中,設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個操作:字節代換、行位移、列混合和輪密鑰加。最后一輪迭代不執行列混合。另外,在第一輪迭代之前,先將明文和原始密鑰進行一次異或加密操作。
上圖為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。狀態矩陣經字節代換后的圖如下:
逆向字節替代
逆向字節替代也就是查詢逆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字節,如下圖所示:
逆向行位移
行移位的逆變換是將狀態矩陣中的每一行執行相反的移位操作,例如AES-128中,狀態矩陣的第0行右移0字節,第1行右移1字節,第2行右移2字節,第3行右移3字節。
列混淆
正向列混淆
列混合變換是通過矩陣相乘來實現的,經行移位后的狀態矩陣與固定的矩陣相乘,得到混淆后的狀態矩陣,如下圖的公式所示:
狀態矩陣中的第j列(0 ≤j≤3)的列混合可以表示為下圖所示:
其中,矩陣元素的乘法和加法都是定義在基于GF(2^8)上的二元運算,并不是通常意義上的乘法和加法。這里涉及到一些信息安全上的數學知識,不過不懂這些知識也行。其實這種二元運算的加法等價于兩個字節的異或,乘法則復雜一點。對于一個8位的二進制數來說,使用域上的乘法乘以(00000010)等價于左移1位(低位補0)后,再根據情況同(00011011)進行異或運算,設S1 = (a7 a6 a5 a4 a3 a2 a1 a0),剛0x02 * S1如下圖所示:
也就是說,如果a7為1,則進行異或運算,否則不進行。
類似地,乘以(00000100)可以拆分成兩次乘以(00000010)的運算:
乘以(0000 0011)可以拆分成先分別乘以(0000 0001)和(0000 0010),再將兩個乘積異或:
逆向列混淆
逆向列混合變換可由下圖的矩陣乘法定義:
可以驗證,逆變換矩陣同正變換矩陣的乘積恰好為單位矩陣。
輪密鑰加
輪密鑰加是將128位輪密鑰Ki同狀態矩陣中的數據進行逐位異或操作,如下圖所示。其中,密鑰Ki中每個字W[4i],W[4i+1],W[4i+2],W[4i+3]為32位比特字,包含4個字節,他們的生成算法下面在下面介紹。輪密鑰加過程可以看成是字逐位異或的結果,也可以看成字節級別或者位級別的操作。也就是說,可以看成S0 S1 S2 S3 組成的32位字與W[4i]的異或運算。
輪密鑰加的逆運算同正向的輪密鑰加運算完全一致,這是因為異或的逆操作是其自身。輪密鑰加非常簡單,但卻能夠影響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加密解密的簡單使用,其中的圖片和大部分內容都是來自一位大神博客中的內容。
本文章也僅限個人學習使用,如果有哪里不對的地方請大佬們多多指正。