IOS 使用AES/ECB/PKCS7Padding 加密、解密数据


AES:加密方式



ECB:工作方式



PKCS5Padding:填充方式(IOS中只有PKCS7Padding,别担心,PKCS5Padding是PKCS7Padding的一个子集,所以使用PKCS7Padding代替也是一样的)



可能用到的框架:



AESCrypt-ObjC-master - Github:https://github.com/Gurpartap/aescrypt





加密中的补位操作:



加密时,如果长度少于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框架的问题吧,暂不深究。