iOS與PHP/Android AES128 ECB NoPadding加密
來自: http://www.henishuo.com/ios-aes128-ecb-nopadding/
前言
談談AES加密,網上有很多的版本,當我沒有真正在加密安全問題前,總以為百度出來某個AES加密算法就可以直接使用,實際上當我真正要做加密時,遇到了很多的坑,原來不是拿過來就能用的。寫下本篇文章,記錄下曾經遇到的坑,嚴防以后再出現同樣的坑。
AES規則
原輸入數據不夠16字節的整數位時,就要補齊。因此就會有padding,若使用不同的padding,那么加密出來的結果也會不一樣。
AES加密算法
蘋果提供給我們的API只有這一個函數用來加密或者解密:
CCCryptorStatus CCCrypt( CCOperation op, /* kCCEncrypt, etc. */ CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */ CCOptions options, /* kCCOptionPKCS7Padding, etc. */ const void *key, size_tkeyLength, const void *iv, /* optional initialization vector */ const void *dataIn, /* optional per op and alg */ size_tdataInLength, void *dataOut, /* data RETURNED here */ size_tdataOutAvailable, size_t*dataOutMoved) __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);
- 其中第一個 CCOperation 只有兩個值,要么是 kCCEncrypt 表示加密,要么是 kCCDecrypt 表示解密。
- 第二個參數表示加密的算法,它只有以下向種類型:
enum { kCCAlgorithmAES128 = 0, kCCAlgorithmAES = 0, kCCAlgorithmDES, kCCAlgorithm3DES, kCCAlgorithmCAST, kCCAlgorithmRC4, kCCAlgorithmRC2, kCCAlgorithmBlowfish }; typedef uint32_tCCAlgorithm;
我們這里使用的是 kCCAlgorithmAES128 表示使用AES128位加密。
- 第三個參數表示選項,這里使用的是 kCCOptionECBMode ,表示ECB:
enum { /* options for block ciphers */ kCCOptionPKCS7Padding = 0x0001, kCCOptionECBMode = 0x0002 /* stream ciphers currently have no options */ }; typedef uint32_tCCOptions;
- 第四個參數表示加密/解密的密鑰。
- 第五個參數keyLength表示密鑰的長度。
- 第六個參數iv是個固定值,通過直接使用密鑰即可。大家一定要注視這個參數,如果安卓、服務端和iOS端不統一,那么加密結果就會不一樣,解密可能能解出來,但是解密后在末尾會出現一些\0、\t之類的。
- 第七個參數dataIn表示要加密/解密的數據。
- 第八個參數dataInLength表示要加密/解密的數據的長度。
- 第九個參數dataOut用于接收加密后/解密后的結果。
- 第十個參數dataOutAvailable表示加密后/解密后的數據的長度。
- 第十一個參數dataOutMoved表示實際加密/解密的數據的長度。(因為有補齊)
加密算法
依賴于第三方庫: GTMBase64 ,這個庫已經幾年沒有維護了,現在還是MRC版本,要使用請到GITHUB查看使用教程,那里有ARC接入說明:
+ (NSString *)hyb_AESEncrypt:(NSString *)plainTextpassword:(NSString *)key { if (key == nil || (key.length != 16 && key.length != 32)) { return nil; } char keyPtr[kCCKeySizeAES128+1]; memset(keyPtr, 0, sizeof(keyPtr)); [keygetCString:keyPtrmaxLength:sizeof(keyPtr)encoding:NSUTF8StringEncoding]; char ivPtr[kCCBlockSizeAES128+1]; memset(ivPtr, 0, sizeof(ivPtr)); [keygetCString:ivPtrmaxLength:sizeof(ivPtr)encoding:NSUTF8StringEncoding]; NSData* data = [plainTextdataUsingEncoding:NSUTF8StringEncoding]; NSUInteger dataLength = [datalength]; int diff = kCCKeySizeAES128 - (dataLength % kCCKeySizeAES128); unsigned long newSize = 0; if(diff > 0) { newSize = dataLength + diff; } char dataPtr[newSize]; memcpy(dataPtr, [databytes], [datalength]); for(int i = 0; i < diff; i++) { // 這里是關鍵,這里是使用NoPadding的 dataPtr[i + dataLength] = 0x0000; } size_tbufferSize = newSize + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); memset(buffer, 0, bufferSize); size_tnumBytesCrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode, [keyUTF8String], kCCKeySizeAES128, ivPtr, dataPtr, sizeof(dataPtr), buffer, bufferSize, &numBytesCrypted); if (cryptStatus == kCCSuccess) { NSData *resultData = [NSDatadataWithBytesNoCopy:bufferlength:numBytesCrypted]; return [GTMBase64stringByEncodingData:resultData]; } free(buffer); return nil; }
對于加密算法,大家一定要注意,保證iOS、安卓、服務端的加密規則是一定的,建議統一使用No Padding的,這里使用No Padding是這樣的:
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接入說明:
+ (NSString *)hyb_AESDecrypt:(NSString *)encryptTextpassword:(NSString *)key { if (key == nil || (key.length != 16 && key.length != 32)) { return nil; } char keyPtr[kCCKeySizeAES128 + 1]; memset(keyPtr, 0, sizeof(keyPtr)); [keygetCString:keyPtrmaxLength:sizeof(keyPtr)encoding:NSUTF8StringEncoding]; char ivPtr[kCCBlockSizeAES128 + 1]; memset(ivPtr, 0, sizeof(ivPtr)); [keygetCString:ivPtrmaxLength:sizeof(ivPtr)encoding:NSUTF8StringEncoding]; NSData *data = [GTMBase64decodeData:[encryptTextdataUsingEncoding:NSUTF8StringEncoding]]; NSUInteger dataLength = [datalength]; size_tbufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_tnumBytesCrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionECBMode, [keyUTF8String], kCCBlockSizeAES128, ivPtr, [databytes], dataLength, buffer, bufferSize, &numBytesCrypted); if (cryptStatus == kCCSuccess) { NSData *resultData = [NSDatadataWithBytesNoCopy:bufferlength:numBytesCrypted]; NSString *decoded=[[NSString alloc]initWithData:resultDataencoding:NSUTF8StringEncoding]; return decoded; } free(buffer); return nil; }
解密時也得跟加密一樣指定為ECB,否則解出來會出現亂碼,或者末尾會出現\0、\t之類的符號。
寫在最后
開發中總會遇到各種坑,網上查了很多的資料,但是終究沒有說明解決的辦法,而是只將自己的代碼放出來。對于剛接觸這方面知識的開發人員來說,是很懵懂的。甚至很多新手會覺得系統就是這樣的,我也沒辦法。其實總會有解決辦法的,關鍵在于與其他各端統一連調。
關注我
Swift/ObjC技術群一:324400294(已滿)
Swift/ObjC技術群二:494669518
ObjC/Swift高級群:461252383(注明年限,新手勿擾)
關注微信公眾號: iOSDevShares
關注新浪微博賬號:標哥Jacky
標哥的GITHUB地址: CoderJackyHuang
支持并捐助
如果您覺得文章對您很有幫忙,希望得到您的支持。您的捐肋將會給予我最大的鼓勵,感謝您的支持!
支付寶捐助 | |||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
![]() |