IOS 使用AES/ECB/PKCS7Padding 加密、解密数据
|
AES:加密方式
ECB:工作方式
PKCS5Padding:填充方式(IOS中只有PKCS7Padding,别担心,PKCS5Padding是PKCS7Padding的一个子集,所以使用PKCS7Padding代替也是一样的)
可能用到的框架:
加密中的补位操作:
加密时,如果长度少于16个字节,需要补满16个字节,补(16-len)个(16-len),例如:@"AAAA"这个节符串是4个字节,16 - 4 = 12,所以需要再补12个十进制的12;解密时,因为加密时补的是十进制1到16,解密时,需要把这部分的补位去掉,逐一判断要解密的字符串,每个字节是不是 char >= 1 && char <= 16,如果是的话,就用0来替换以前的值,直到结束,原理是这样,在IOS中一般不需要我们自己进行补位操作,底层会帮我们完成。
字符编码:
常见的字符编码有:UTF-8、ASCII、Base64、十六进制等等,不要使用UTF-8,加密过程是使用UTF-8完成的,但是加密完后的NSData无法通过UTF-8编码格式转出成NSString,推荐使用Base64编码和十六进制编码,下面举例:
使用Base64编码:(如果不能解密中文请使用十六进制或其他编码)
+ (NSData*)base64DataFromString:(NSString*)string;
+ (NSString*)base64StringFromData:(NSData*)data length:(NSUInteger)length; |
使用十六进制编码:(需要注意的是:加、解密过程中是使用UTF-8完成的,在加密时,只需要将加密结果NSData对象转为十六进制字符串,而解密时,也只需要将获取到的16进制字符串转换成NSData然后再进行解密,切记)
NSData*cipher = [XNFunctionconvertHexStrToData:content];
//方法如下(注意:这里的十六进制是指十六进制字符串,不是0x000002)
//十六进制转换为NSData
+ (NSData*)convertHexStrToData:(NSString*)str {
if (!str || [str length] ==0) {
return nil;
}
NSMutableData *hexData = [[NSMutableDataalloc]initWithCapacity:8];
NSRange range;
if ([str length] %2==0) {
range = NSMakeRange(0,2);
} else {
range = NSMakeRange(0,1);
}
for (NSIntegeri = range.location; i < [str length]; i +=2) {
unsigned int anInt;
NSString *hexCharStr = [str substringWithRange:range];
NSScanner *scanner = [[NSScanneralloc]initWithString:hexCharStr];
[scanner scanHexInt:&anInt];
NSData *entity = [[NSDataalloc]initWithBytes:&anIntlength:1];
[hexData appendData:entity];
range.location+= range.length;
range.length=2;
}
NSLog(@"hexdata: %@", hexData);
return hexData;
}
//NSData转换为16进制
+ (NSString*)convertDataToHexStr:(NSData*)data {
if (!data || [data length] ==0) {
return @"";
}
NSMutableString *string = [[NSMutableStringalloc]initWithCapacity:[datalength]];
[data enumerateByteRangesUsingBlock:^(constvoid*bytes,NSRangebyteRange,BOOL*stop) {
unsigned char *dataBytes = (unsignedchar*)bytes;
for (NSIntegeri =0; i < byteRange.length; i++) {
NSString *hexStr = [NSStringstringWithFormat:@"%x", (dataBytes[i]) & 0xff];
if ([hexStr length] ==2) {
[string appendString:hexStr];
} else {
[string appendFormat:@"0%@", hexStr];
}
}
}];
return string;
}
|
进入正题,我们使用框架加、解密:
NSString*str =@"ABC123!@#中文";
NSString*key =@"F8hfdtgfu**0Ka0";
NSData*password = [[keydataUsingEncoding:NSUTF8StringEncoding]MD5Sum];
CCCryptorStatusstatus =kCCSuccess;
NSData*data = [strdataUsingEncoding:NSUTF8StringEncoding];
//加密
NSData* result = [data dataEncryptedUsingAlgorithm:kCCAlgorithmAES128
key:password
options:kCCOptionPKCS7Padding|kCCOptionECBMode
error:&status];
//解密:
NSData*encrypted = [resultdecryptedDataUsingAlgorithm:kCCAlgorithmAES128
key:password //字符串key够16位,可以直接传进去,不用转成NSdata也行
options:kCCOptionPKCS7Padding|kCCOptionECBMode
error:&status];
plainString = [[NSStringalloc]initWithData:encryptedencoding:NSUTF8StringEncoding];
NSLog(@"%@", plainString); //输出:ABC123!@#中文
|
问题来了,有时候后台(用Java代码使用十六进制编码加密的)传过来的密文是十六进制的字符串,这时候使用这个框架可能会解不开。正确流程应该是这样的,首先拿到密文后,需要将其转换成NSdata,而IOS中没有提供直接将十六进制字符串转成NSdata的API,所以我找来了两个个工具函数,直接拿来用,在上文中有提到,然后再将这个NSData解密,解密后将结果过按UTF-8编码将其转成NSString,结果为nil,苹果官方文档中提到如果NSData中含有非UTF-8编码时就会返回nil,这里很奇怪,明明已经在拿到密文时就使用函数convertHexStrToData将其转成了NSData(Hex->UTF-8->NSData),却里无法转出NSString。
NSString*plainString =nil;
NSString*key =@"%F8hfdtgfu**0Ka0";
CCCryptorStatusstatus =kCCSuccess;
//十六进制字符串
NSData*data = [selfconvertHexStrToData:text];
//解密:
NSData*encrypted = [datadecryptedDataUsingAlgorithm:kCCAlgorithmAES128
key:key
options:kCCOptionPKCS7Padding|kCCOptionECBMode
error:&status];
plainString = [selfconvertDataToHexStr:encrypted]; //转出成功,但看起来还是十六进制字符串,于是进行下步
plainString = [selfstringFromHexString:plainString];//十六进制字符串转成普通字符串
NSLog(@"%@", plainString); //失败
+ (NSString*)stringFromHexString:(NSString*)hexString {//
char*myBuffer = (char*)malloc((int)[hexStringlength] /2+1);
bzero(myBuffer, [hexStringlength] /2+1);
for (inti =0; i < [hexStringlength] -1; i +=2) {
unsigned int anInt;
NSString * hexCharStr = [hexString substringWithRange:NSMakeRange(i,2)];
NSScanner * scanner = [[NSScanneralloc]initWithString:hexCharStr];
[scanner scanHexInt:&anInt];
myBuffer[i / 2] = (char)anInt;
}
NSString *unicodeString = [NSStringstringWithCString:myBufferencoding:4];
NSLog(@"字符串:%@",unicodeString);
return unicodeString;
}
|
这找了很久原因,无果,后来在google上看到了另一种加、解密的步骤,直接抠下来用
//16位的key,补位操作省略
NSString*key =@"F8hfdtgfu**0Ka0";
//HexString -> NSData
NSData*cipher = [selfconvertHexStrToData:text];
//解密
NSData*plain = [cipherAES256DecryptWithKey:key];
//直接是用UTF-8编码转出
NSString*plainString = [[NSStringalloc]initWithData:plainencoding:NSUTF8StringEncoding];
NSLog(@"[解密结果] :%@", plainString); //解密成功
|
于是我查看了这两个方法的加密步骤,确实有所不同:
AESCrypt-ObjC-master的解密步骤:
1.自动识别参数key和参数iv的类型,如果传入的是NSString,就将其按UTF-8编码转成NSData
2.调用FixKeyLengths将key和iv按 传入的参数algorithm(CCAlgorithm)自动进行补位
3.调用CCCryptorCreate创建一个Cryptorr
/*创建 cryptor
* 参数1:解密
* 参数2:填充方式,这里传入kCCAlgorithmAES128
* 参数3:工作模式:kCCOptionPKCS7Padding | kCCOptionECBMode
* 参数4:key
* 参数5:key的长度
* 参数6:iv
*参数7:CCCryptorRef cryptor = NULL;
*/
CCCryptorCreate(kCCEncrypt, algorithm, options, [keyDatabytes], [keyDatalength], [ivDatabytes], &cryptor );
NSData* resultData = [self_runCryptor: cryptorresult: &status];
|
4.更新Crypto,最终得到resultData
- (NSData*) _runCryptor: (CCCryptorRef) cryptor result: (CCCryptorStatus*) status {
size_tbufsize = CCCryptorGetOutputLength( cryptor, (size_t)[selflength],true );
void* buf =malloc( bufsize );
size_tbufused = 0;
size_tbytesTotal = 0;
/*更新 cryptor
*参数1:cryptor
*参数2:密文
*参数3:密文大小
*参数4:buf
*参数5:bufsize
*参数6:...
*/
*status =CCCryptorUpdate( cryptor, [selfbytes], (size_t)[selflength],buf, bufsize, &bufused );
if( *status !=kCCSuccess) {
free( buf );
return( nil );
}
bytesTotal += bufused;
// From Brent Royal-Gordon (Twitter: architechies):
// Need to update buf ptr past used bytes when calling CCCryptorFinal()
*status =CCCryptorFinal( cryptor, buf + bufused, bufsize - bufused, &bufused );
if( *status != kCCSuccess ) {
free( buf );
return( nil );
}
bytesTotal += bufused;
return( [NSDatadataWithBytesNoCopy: buflength: bytesTotal] );
}
|
另一种解密步骤:(也就是本次需求中遇到的AES/ECB/PKCS5Padding + 16进制编码)
- (NSData*)AES256DecryptWithKey:(NSString*)key //解密
{
//AES的密钥长度有128字节、192字节、256字节几种,这里举出可能存在的最大长度
charkeyPtr[kCCKeySizeAES256+1];
bzero(keyPtr,sizeof(keyPtr));
[keygetCString:keyPtrmaxLength:sizeof(keyPtr)encoding:NSUTF8StringEncoding];
//密文的长度
NSUIntegerdataLength = [selflength];
//密文长度+补位长度
size_tbufferSize = dataLength +kCCBlockSizeAES128;
//为解密结果开辟空间
void*buffer =malloc(bufferSize);
size_t numBytesDecrypted = 0;
/* kCCDecrypt:解密
* kCCAlgorithmAES128:加密方式
* kCCOptionPKCS7Padding | kCCOptionECBMode:工作模式
* keyPtr:UTF-8格式的key
* kCCBlockSizeAES128:按16位长度解密
* iv:AES不用iv
* [self bayes]:密文
* ...
*/
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,kCCAlgorithmAES128,
kCCOptionPKCS7Padding|kCCOptionECBMode,
keyPtr, kCCBlockSizeAES128,
NULL,
[selfbytes], dataLength,
buffer, bufferSize,
&numBytesDecrypted);
if (cryptStatus == kCCSuccess) {
return [NSDatadataWithBytesNoCopy:bufferlength:numBytesDecrypted];
}
free(buffer);
return nil;
}
|
建议使用第二种方式,
根据不同加密方式和填充方式传入相应的参数即可,其实第一种使用CCCryptor应该也可以,但是不知道为什么在解密十六进制编码时不成功,同样是API/usr/include/CommonCrypto中的API,虽然方法不同,但底层实现应该都是一样的,估计是AESCrypt-ObjC-master框架的问题吧,暂不深究。