前言
談談AES加密,網上有很多的版本,當我沒有真正在加密安全問題前,總以為百度出來某個AES加密算法就可以直接使用,實際上當我真正要做加密時,遇到了很多的坑,原來不是拿過來就能用的。寫下本篇文章,記錄下曾經遇到的坑,嚴防以后再出現同樣的坑。
AES規則
原輸入數據不夠16字節的整數位時,就要補齊。因此就會有padding,若使用不同的padding,那么加密出來的結果也會不一樣。
AES加密算法
蘋果提供給我們的API只有這一個函數用來加密或者解密:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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
只有兩個值,要么是kCCEncrypt
表示加密,要么是kCCDecrypt
表示解密。 - 第二個參數表示加密的算法,它只有以下向種類型:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
enum {
kCCAlgorithmAES128 = 0,
kCCAlgorithmAES = 0,
kCCAlgorithmDES,
kCCAlgorithm3DES,
kCCAlgorithmCAST,
kCCAlgorithmRC4,
kCCAlgorithmRC2,
kCCAlgorithmBlowfish
};
typedef uint32_t CCAlgorithm;
|
我們這里使用的是kCCAlgorithmAES128
表示使用AES128位加密。
- 第三個參數表示選項,這里使用的是
kCCOptionECBMode
,表示ECB:
1
2
3
4
5
6
7
8
9
|
enum {
/* options for block ciphers */
kCCOptionPKCS7Padding = 0x0001,
kCCOptionECBMode = 0x0002
/* stream ciphers currently have no options */
};
typedef uint32_t CCOptions;
|
- 第四個參數表示加密/解密的密鑰。
- 第五個參數keyLength表示密鑰的長度。
- 第六個參數iv是個固定值,通過直接使用密鑰即可。大家一定要注視這個參數,如果安卓、服務端和iOS端不統一,那么加密結果就會不一樣,解密可能能解出來,但是解密后在末尾會出現一些\0、\t之類的。
- 第七個參數dataIn表示要加密/解密的數據。
- 第八個參數dataInLength表示要加密/解密的數據的長度。
- 第九個參數dataOut用於接收加密后/解密后的結果。
- 第十個參數dataOutAvailable表示加密后/解密后的數據的長度。
- 第十一個參數dataOutMoved表示實際加密/解密的數據的長度。(因為有補齊)
加密算法
依賴於第三方庫:GTMBase64,這個庫已經幾年沒有維護了,現在還是MRC版本,要使用請到GITHUB查看使用教程,那里有ARC接入說明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
+ (NSString *)hyb_AESEncrypt:(NSString *)plainText password:(NSString *)key {
if (key == nil || (key.length != 16 && key.length != 32)) {
return nil;
}
char keyPtr[kCCKeySizeAES128+1];
memset(keyPtr, 0, sizeof(keyPtr));
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
char ivPtr[kCCBlockSizeAES128+1];
memset(ivPtr, 0, sizeof(ivPtr));
[key getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
NSData* data = [plainText dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger dataLength = [data length];
int diff = kCCKeySizeAES128 - (dataLength % kCCKeySizeAES128);
unsigned long newSize = 0;
if(diff > 0) {
newSize = dataLength + diff;
}
char dataPtr[newSize];
memcpy(dataPtr, [data bytes], [data length]);
for(int i = 0; i < diff; i++) {
// 這里是關鍵,這里是使用NoPadding的
dataPtr[i + dataLength] = 0x0000;
}
size_t bufferSize = newSize + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
memset(buffer, 0, bufferSize);
size_t numBytesCrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionECBMode,
[key UTF8String],
kCCKeySizeAES128,
ivPtr,
dataPtr,
sizeof(dataPtr),
buffer,
bufferSize,
&numBytesCrypted);
if (cryptStatus == kCCSuccess) {
NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
return [GTMBase64 stringByEncodingData:resultData];
}
free(buffer);
return nil;
}
|
對於加密算法,大家一定要注意,保證iOS、安卓、服務端的加密規則是一定的,建議統一使用No Padding的,這里使用No Padding是這樣的:
1
2
3
4
5
6
|
for(int i = 0; i < diff; i++) {
// 這里是關鍵,這里是使用NoPadding的
dataPtr[i + dataLength] = 0x0000;
}
|
其實所謂Padding就是指在位數不夠需要補齊時,使用什么來填充,而No Padding就是使用16個0,對應0x0000.如果三端不統一,加密出來就算能解密,也會出現一些奇怪的字符,甚至會有部分亂碼。
另外,這里使用的是kCCOptionECBMode
,也就是ECB。在安卓端和PHP端,也得使用ECB。在調試過程中,發現PHP使用CBC解密不了IOS端的。於是改成了使用ECB。
解密算法
依賴於第三方庫:GTMBase64,這個庫已經幾年沒有維護了,現在還是MRC版本,要使用請到GITHUB查看使用教程,那里有ARC接入說明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
+ (NSString *)hyb_AESDecrypt:(NSString *)encryptText password:(NSString *)key {
if (key == nil || (key.length != 16 && key.length != 32)) {
return nil;
}
char keyPtr[kCCKeySizeAES128 + 1];
memset(keyPtr, 0, sizeof(keyPtr));
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
char ivPtr[kCCBlockSizeAES128 + 1];
memset(ivPtr, 0, sizeof(ivPtr));
[key getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
NSData *data = [GTMBase64 decodeData:[encryptText dataUsingEncoding:NSUTF8StringEncoding]];
NSUInteger dataLength = [data length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesCrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionECBMode,
[key UTF8String],
kCCBlockSizeAES128,
ivPtr,
[data bytes],
dataLength,
buffer,
bufferSize,
&numBytesCrypted);
if (cryptStatus == kCCSuccess) {
NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
NSString *decoded=[[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
return decoded;
}
free(buffer);
return nil;
}
|
解密時也得跟加密一樣指定為ECB,否則解出來會出現亂碼,或者末尾會出現\0、\t之類的符號。
寫在最后
開發中總會遇到各種坑,網上查了很多的資料,但是終究沒有說明解決的辦法,而是只將自己的代碼放出來。對於剛接觸這方面知識的開發人員來說,是很懵懂的。甚至很多新手會覺得系統就是這樣的,我也沒辦法。其實總會有解決辦法的,關鍵在於與其他各端統一連調。
-
一行代碼實現加密
更新:MD5加密是單向的,只能加密不能解密(破解除外)。標題可能會引起讀者誤解,已經改正,感謝Li_Cheng同學的提醒,另外筆者發現Li_Cheng同學有篇MD5加密更為詳盡的文章,推薦閱讀:iOS開發 關於MD5加密的相關使用
java端的加密解密,讀者可以看我同事的這篇文章http://www.jianshu.com/p/98569e81cc0b
最近做了一個移動項目,是有服務器和客戶端類型的項目,客戶端是要登錄才行的,服務器也會返回數據,服務器是用Java開發的,客戶端要同時支持多平台(Android、iOS),在處理iOS的數據加密的時候遇到了一些問題。起初采取的方案是DES加密,老大說DES加密是對稱的,網絡抓包加上反編譯可能會被破解,故采取RSA方式加密。RSA加密時需要公鑰和私鑰,客戶端保存公鑰加密數據,服務器保存私鑰解密數據。(iOS端公鑰加密私鑰解密、java端公鑰加密私鑰解密,java端私鑰加密公鑰解密都容易做到,iOS不能私鑰加密公鑰解密,只能用於驗簽)。
問題
問題1:iOS端公鑰加密的數據用Java端私鑰解密。
iOS無論使用系統自帶的sdk函數,用mac產生的或者使用java的jdk產生的公鑰和私鑰,進行加密解密自己都可以使用。不過ios加密,java解密,或者反過來就不能用了。要么是無法創建報告個-9809或-50的錯誤,要么解出來是亂碼。ios系統函數種只有用公鑰加密,私鑰解密的方式。而公鑰加密每次結果都不同。
MAC上生成公鑰、私鑰的方法,及使用
- 1.打開終端,切換到自己想輸出的文件夾下
- 2.輸入指令:
openssl
(openssl是生成各種秘鑰的工具,mac已經嵌入
- 3.輸入指令:
genrsa -out rsa_private_key.pem 1024
(生成私鑰,java端使用的)
- 4.輸入指令:
rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
(生成公鑰)
- 5.輸入指令:
pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
(私鑰轉格式,在ios端使用私鑰解密時用這個私鑰)
注意:在MAC上生成三個.pem格式的文件,一個公鑰,兩個私鑰,都可以在終端通過指令vim xxx.pem 打開,里面是字符串,第三步生成的私鑰是java端用來解密數據的,第五步轉換格式的私鑰iOS端可以用來調試公鑰、私鑰解密(因為私鑰不留在客戶端)
詳細步驟
問題2:服務器返回數據也要加密,老大打算用java私鑰加密,ios用公鑰解密(由於iOS做不到用私鑰加密公鑰解密,只能私鑰加密公鑰驗簽),所以這種方案也有問題。
通過看一些大牛的介紹,了解了iOS常用的加密方式
- 1 通過簡單的URLENCODE + BASE64編碼防止數據明文傳輸
- 2 對普通請求、返回數據,生成MD5校驗(MD5中加入動態密鑰),進行數據完整性(簡單防篡改,安全性較低,優點:快速)校驗
- 3 對於重要數據,使用RSA進行數字簽名,起到防篡改作
- 4 對於比較敏感的數據,如用戶信息(登陸、注冊等),客戶端發送使用RSA加密,服務器返回使用DES(AES)加密
原因:客戶端發送之所以使用RSA加密,是因為RSA解密需要知道服務器私鑰,而服務器私鑰一般盜取難度較大;如果使用DES的話,可以通過破解客戶端獲取密鑰,安全性較低。而服務器返回之所以使用DES,是因為不管使用DES還是RSA,密鑰(或私鑰)都存儲在客戶端,都存在被破解的風險,因此,需要采用動態密鑰,而RSA的密鑰生成比較復雜,不太適合動態密鑰,並且RSA速度相對較慢,所以選用DES)
所以此次加密,我們選擇了第四種加密方式
加密方式
ios端進行DES加密、解密時非常方便
1、引入頭文件 #import "DES3Util.h" 2、加密時調用類方法 +(NSString *) encryptUseDES:(NSString *)plainText key:(NSString *)key; 3、解密時調用類方法 +(NSString *)decryptUseDES:(NSString *)cipherText key:(NSString *)key;
ios端進行RSA加密、解密時非常方便
1、引入頭文件 #import "RSAUtil.h" 2、公鑰加密時調用類方法: + (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey; + (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey; 3、私鑰解密時調用類方法 + (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey; + (NSData *)decryptData:(NSData *)data privateKey:(NSString *)privKey;
ios端進行MD5加密、解密時非常方便
1、引入頭文件 #import "MD5Util" 2、加密時調用方法:- (NSString *)md5:(NSString *)str;
ios端進行AES加密、解密時非常方便
1、引入頭文件 #import "AES.h" 2、加密時調用方法 + (NSString *)encrypt:(NSString *)message password:(NSString *)password; 2、解密時調用的方法 + (NSString *)decrypt:(NSString *)base64EncodedString password:(NSString *)password;
-
MD5加密
iOS中提供了很多種加密算法,對於存儲密碼,可以使用不可逆的MD5加密。
使用MD5加密需要導入頭文件:
''#import <CommonCrypto/CommonDigest.h>
##### 簡單的MD5加密 + ( NSString *)md5String:( NSString *)str { const char *myPasswd = [str UTF8String ]; unsigned char mdc[ 16 ]; CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc); NSMutableString *md5String = [ NSMutableString string ]; for ( int i = 0 ; i< 16 ; i++) { [md5String appendFormat : @"%02x" ,mdc[i]]; } return md5String; } ##### 復雜一些的MD5加密 + ( NSString *)md5String:( NSString *)str { const char *myPasswd = [str UTF8String ]; unsigned char mdc[ 16 ]; CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc); NSMutableString *md5String = [ NSMutableString string ]; [md5String appendFormat : @"%02x" ,mdc[ 0 ]]; for ( int i = 1 ; i< 16 ; i++) { [md5String appendFormat : @"%02x" ,mdc[i]^mdc[ 0 ]];
-
AES加密
高級加密標准(Advanced Encryption Standard,AES),又稱Rijndael加密法。 以下實現代碼中分別為NSData和NSString增加了一個Category。使用時直接調用即可。
需要注意的是,AES並不能作為HASH算法,加密並解密后的結果,並不一定與原文相同,使用時請注意進行結果驗算。例如解密原文的長度,格式規則等。 NG實例
原文:170987350 密碼:170
Objective-c的AES加密和解密算法的具體實現代碼如下: 1.拓展NSData,增加AES256加密方法
//
//NSData+AES256.h // #import <Foundation/Foundation.h> #import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonCryptor.h> @interface NSData(AES256) -(NSData *) aes256_encrypt:(NSString *)key; -(NSData *) aes256_decrypt:(NSString *)key; @end // //NSData+AES256.m // #import "NSData+AES256.h" @implementation NSData(AES256) - (NSData *)aes256_encrypt:(NSString *)key //加密 { char keyPtr[kCCKeySizeAES256+1]; bzero(keyPtr, sizeof(keyPtr)); [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; NSUInteger dataLength = [self length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesEncrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCBlockSizeAES128, NULL, [self bytes], dataLength, buffer, bufferSize, &numBytesEncrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted]; } free(buffer); return nil; } - (NSData *)aes256_decrypt:(NSString *)key //解密 { char keyPtr[kCCKeySizeAES256+1]; bzero(keyPtr, sizeof(keyPtr)); [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; NSUInteger dataLength = [self length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesDecrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCBlockSizeAES128, NULL, [self bytes], dataLength, buffer, bufferSize, &numBytesDecrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted]; } free(buffer); return nil; } @end |
2.拓展NSString,增加AES256加密方法,需要導入NSData+AES256.h
//
//NSString +AES256.h // #import <Foundation/Foundation.h> #import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonCryptor.h> #import "NSData+AES256.h" @interface NSString(AES256) -(NSString *) aes256_encrypt:(NSString *)key; -(NSString *) aes256_decrypt:(NSString *)key; @end // //NSString +AES256.h // @implementation NSString(AES256) -(NSString *) aes256_encrypt:(NSString *)key { const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding]; NSData *data = [NSData dataWithBytes:cstr length:self.length]; //對數據進行加密 NSData *result = [data aes256_encrypt:key]; //轉換為2進制字符串 if (result && result.length > 0) { Byte *datas = (Byte*)[result bytes]; NSMutableString *output = [NSMutableString stringWithCapacity:result.length * 2]; for(int i = 0; i < result.length; i++){ [output appendFormat:@"%02x", datas[i]]; } return output; } return nil; } -(NSString *) aes256_decrypt:(NSString *)key { //轉換為2進制Data NSMutableData *data = [NSMutableData dataWithCapacity:self.length / 2]; unsigned char whole_byte; char byte_chars[3] = {'\0','\0','\0'}; int i; for (i=0; i < [self length] / 2; i++) { byte_chars[0] = [self characterAtIndex:i*2]; byte_chars[1] = [self characterAtIndex:i*2+1]; whole_byte = strtol(byte_chars, NULL, 16); [data appendBytes:&whole_byte length:1]; } //對數據進行解密 NSData* result = [data aes256_decrypt:key]; if (result && result.length > 0) { return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding]autorelease]; } return nil; } @end |