针对某移动办公App的网络安全分析

0 前言

逆向的目标之一是评定App安全等级,找到存在的安全隐患,并以有效手段进行反破解。之前实现任意位置打卡的几种方法,都是修改实际的GPS位置信息,并没有从网络、代码层面进行深入的分析。乘着假期,将网络请求的流程梳理出来,看看能否从网络层,获取相关的请求、参数及加密方法。

1 网络流程分析

Charels抓包,网络请求走的都是HTTPS,没有可参考的价值。

回归代码,结合头文件,网络请求用的AFNetworking。既然如此,直接Hook住AFHTTPSessionManager类中最常用的POST请求方法 - POST:parameters:progress:success:failure:,将url、参数打印出来:

//Hook的class
CHDeclareClass(AFHTTPSessionManager);

// - (id)POST:(id)arg1 parameters:(id)arg2 progress:(id)arg3 success:(id)arg4 failure:(id)arg5
CHMethod(5, void, AFHTTPSessionManager, POST, id, arg1, parameters, id, arg2, progress, id, arg3, success, id, arg4, failure, id, arg5)
{
    NSLog(@"%s::%@\n %@", __func__, arg1, arg2);
    return CHSuper(5, AFHTTPSessionManager, POST, arg1, parameters,arg2, progress, arg3, success, arg4, failure, arg5);
}

__attribute__((constructor)) static void entry()
{
    CHLoadLateClass(AFHTTPSessionManager);
    CHClassHook(5, AFHTTPSessionManager, POST, parameters, progress, success, failure);
}

1.1 登录请求分析

接下来,打开App进行登录,看看发送了哪些内容。打印信息如下:

https://app.xxxxtech.com/GetAndroidDataService.svc/LoginVerify
{
    jsonData = 0e10de0e07c7f127d43b326cb30ceac2566b2dfabd8facbd431e7e31708ffaf8bb310fd0abd77570e10fa3a61c36aac11261b59889c0aa606b64cbde9f43d6814503211a0e0a2a55552646c393e8e9fa72ab0703685cf19308f9a16522d58e405874fa9956a40a99313b01cdb6d3eab702019d3a2eedd819b0a2fab032a8fff52258133eb42b828a80fd4d15df199f4ab83cd4a2df67a5fac0906694ede697abde35c29587de9ca894036f785f0c2d94164a363cb6e92c4a0072d21e1f8b9b0843b1c2af7c90d3c89117318ef8e1c86fa32f99ec9182d57b5191a8e10ad0d80e6ee8de8cb0a3398be971e5e261d77b93858ed103b91af91570a832a95f42ac658737739cd60f46920d21b1154158a0ecae8f8cb7dbcb2ac6c3894e51a290c033715f11bb9e3ce18cb48429f4e5ba69be4e7d17861b355d96ce64d8ac472fafcf04ed891f29a98fa276086fc4d57d10ca;
}

得到请求url 【https://app.xxxxtech.com/GetAndroidDataService.svc/LoginVerify

参数是字典格式NSDictionary<NSString *, NSString *>的数据,key值为 jsonData,value值是一串很长的字符串,显然是加密过的。单从value的字符串形式来看,是由十六进制格式的字符组成的。可能是某种加密方式与MD5的结合,但如果进行了MD5,服务端是无法解析数据的;App和服务采用对称加密的方法最常用,AES/DES等,这里有可能先进行了对称加密,再将每个字符进行转换。如果能找到原始的请求参数、加密方法,整个网络请求层应该都可以破解掉了。

1.2 请求回溯

要找到原始的参数,就需要找到哪个类调用了POST方法。然后往前一步一步回溯,将每个步骤串起来,就可以倒推网络请求的流程。

借助Hopper,搜索字符串POST:parameters:progress:success:failure:,发现ServiceUtil中的几个方法使用了:

queryService:
queryListService:
updateArrayService:
updateService:

POST.png

登录一般只是检验密码,hook住queryService,打印请求参数类型,确实有相应的输出:

UserInfoModel

进一步分析,queryService并没有被其他类直接调用,而是通过在RegisteredServicesMonitor函数内,注册xxxx_MOBILE_ServiceQuery的通知方法被动调用的。

void -[ServiceUtil RegisteredServicesMonitor](void * self, void * _cmd) {
   ...
    r0 = [NSNotificationCenter defaultCenter];
    r0 = [r0 retain];
    stack[2032] = r0;
    _objc_msgSend(r0, *r0, self, @selector(queryService:), @"xxxx_MOBILE_ServiceQuery", 0x0);
    [stack[2032] release];
   ...
    return;
}

搜索通知xxxx_MOBILE_ServiceQuery,再根据[LoginViewController login]伪代码,将整个代码调用顺序串起来,最终发现参数通过DESUtil类进行加密:

Login.png

2 加密分析

整个请求的流程清晰了,回过头来看UserInfoModel是如何转化为成十六进制字符串的。

queryService的参数是在ServiceUtil类的函数dictionaryFormQueryData被转化了,具体过程分为两步:

  • 模型转字典

先用UserInfoModel对象的convertToUpdateDictionary 方法(其中敏感信息FItemNumber、FPassword作了隐藏处理),将对象转化成字典结构

{
    FAppVersion = "v4.0.2.Basics (51)";
    FItemNumber = *****;
    FMobileType = IOS;
    FModuleId = 2990;
    FOSVersion = "10.3.2";
    FPassword = "*****";
    FVerSion = 51;
}
  • 字典转查询条件、并加密
    ObjectForDesAndReturnData函数内,将前面步骤的字典转化成加密的字符串,并组成新的字典,作为请求的参数
{
    jsonData = 0e10de0e07c7f127d43b326cb30ceac25...
    }

2.1 DES加密

进入最终的加密函数 [DESUtil doCipher:key:context:],查看伪代码:

void * +[DESUtil doCipher:key:context:](void * self, void * _cmd, void * arg2, void * arg3, unsigned int ret_addr) {
    r7 = (sp - 0x14) + 0xc;
    sp = sp - 0x1a4;
    var_20 = *___stack_chk_guard;
    objc_storeStrong(r7 - 0x2c, arg2);
    objc_storeStrong(r7 - 0x30, arg3);
    var_34 = ret_addr;
    var_3C = 0x0;
    if (var_34 == 0x1) {
            ...
    }
    else {
            r0 = [0x0 dataUsingEncoding:0x4, r0];
            r7 = r7;
            r0 = [r0 retain];
            var_C8 = r0;
            r1 = var_3C;
            var_3C = [r0 mutableCopy];
            [r1 release];
            [var_C8 release];
    }
    r0 = [0x0 dataUsingEncoding:0x4, r0];
    r7 = r7;
    r0 = [r0 retain];
    var_60 = [r0 mutableCopy];
    [r0 release];
    [var_60 setLength:0x8, r0];
    var_68 = [0x0 length] + 0x8 & 0xfffffff8;
    var_64 = malloc(var_68);
    __memset_chk();
    var_F0 = [objc_retainAutorelease(var_60) bytes];
    var_F8 = [var_60 length];
    *(r7 - 0x100) = [objc_retainAutorelease(var_60) bytes];
    objc_retainAutorelease(var_3C);
    *((r7 - 0x100) + 0xfffffffffffffffc) = _objc_msgSend;
    *((r7 - 0x100) + 0xfffffffffffffff8) = (*((r7 - 0x100) + 0xfffffffffffffffc))();
    *((r7 - 0x100) + 0xfffffffffffffff4) = _objc_msgSend;
    r2 = *((r7 - 0x100) + 0xfffffffffffffff4);
    *((r7 - 0x100) + 0xfffffffffffffff0) = (r2)(var_3C, @selector(length), r2, var_3C);
    *(var_34 + 0xffffffffffffffec) = 0x1;
    r12 = *(var_34 + 0xffffffffffffffec);
    *(var_34 + 0xffffffffffffffe8) = r7 - 0x6c;
    *(var_34 + 0xffffffffffffffe4) = var_64;
    var_70 = 0x0;
    *((r7 - 0x100) + 0xffffffffffffffe0) = CCCrypt(var_34, 0x1, r12, var_F0, var_F8, *(r7 - 0x100), *((r7 - 0x100) + 0xfffffffffffffff8), *((r7 - 0x100) + 0xfffffffffffffff0), *((r7 - 0x100) + 0xffffffffffffffe4), var_68, *((r7 - 0x100) + 0xffffffffffffffe8));
    if (var_34 == 0x1) {
           ...
    }
    else {
            *((r7 - 0x100) + 0xffffffffffffffc8) = _objc_msgSend;
            r0 = (*((r7 - 0x100) + 0xffffffffffffffc8))(@class(NSData), @selector(dataWithBytes:length:), var_64, 0x0);
            r7 = r7;
            var_74 = [r0 retain];
            free(var_64);
            *((r7 - 0x100) + 0xffffffffffffffc4) = _objc_msgSend;
            r2 = *((r7 - 0x100) + 0xffffffffffffffc4);
            (r2)(@class(NSMutableString), @selector(alloc), r2);
            *((r7 - 0x100) + 0xffffffffffffffc0) = @"";
            r3 = *((r7 - 0x100) + 0xffffffffffffffc0);
            *((r7 - 0x100) + 0xffffffffffffffbc) = _objc_msgSend;
            var_78 = (*((r7 - 0x100) + 0xffffffffffffffbc))();
            objc_retainAutorelease(var_74);
            *((r7 - 0x100) + 0xffffffffffffffb8) = _objc_msgSend;
            var_7C = (*((r7 - 0x100) + 0xffffffffffffffb8))();
            var_80 = 0x0;
            do {
                    *((r7 - 0x100) + 0xffffffffffffffb4) = _objc_msgSend;
                    r3 = *((r7 - 0x100) + 0xffffffffffffffb4);
                    *((r7 - 0x100) + 0xffffffffffffffb0) = var_80;
                    if (*((r7 - 0x100) + 0xffffffffffffffb0) >= (r3)(var_74, @selector(length), var_80, r3)) {
                        break;
                    }
                    s0 = *@"%x";
                    *((r7 - 0x100) + 0xffffffffffffffac) = @"%x";
                    *((r7 - 0x100) + 0xffffffffffffffa8) = _objc_msgSend;
                    r0 = (*((r7 - 0x100) + 0xffffffffffffffa8))(@class(NSString), @selector(stringWithFormat:), *((r7 - 0x100) + 0xffffffffffffffac), s0 & 0xff);
                    r7 = r7;
                    var_84 = [r0 retain];
                    *((r7 - 0x100) + 0xffffffffffffffa4) = _objc_msgSend;
                    r2 = *((r7 - 0x100) + 0xffffffffffffffa4);
                    if ((r2)(var_84, @selector(length), r2) == 0x1) {
                            r2 = *(("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics" | 0x2c0000) + 0x4bd9c);
                            *((r7 - 0x100) + 0xffffffffffffffa0) = @"0%@";
                            *((r7 - 0x100) + 0xffffffffffffff9c) = _objc_msgSend;
                            r1 = r2;
                            r2 = *((r7 - 0x100) + 0xffffffffffffffa0);
                            r12 = *((r7 - 0x100) + 0xffffffffffffff9c);
                            *((r7 - 0x100) + 0xffffffffffffff98) = var_78;
                            r0 = (r12)(@class(NSString), r1, r2, var_84);
                            r0 = [r0 retain];
                            r3 = *((r7 - 0x100) + 0xffffffffffffff98);
                            *((r7 - 0x100) + 0xffffffffffffff94) = r0;
                            r0 = r3;
                            *((r7 - 0x100) + 0xffffffffffffff90) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff94);
                            r3 = *((r7 - 0x100) + 0xffffffffffffff90);
                            r0 = (r3)(r0, @selector(stringByAppendingString:), r2, r3);
                            r7 = r7;
                            r0 = [r0 retain];
                            *((r7 - 0x100) + 0xffffffffffffff8c) = r0;
                            *((r7 - 0x100) + 0xffffffffffffff88) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff88);
                            r1 = var_78;
                            var_78 = (r2)(r0, @selector(mutableCopy), r2, r0);
                            [r1 release];
                            r0 = *((r7 - 0x100) + 0xffffffffffffff8c);
                            [r0 release];
                            r0 = *((r7 - 0x100) + 0xffffffffffffff94);
                            [r0 release];
                    }
                    else {
                            *((r7 - 0x100) + 0xffffffffffffff84) = @"%@";
                            *((r7 - 0x100) + 0xffffffffffffff80) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff84);
                            r12 = *((r7 - 0x100) + 0xffffffffffffff80);
                            *((r7 - 0x100) + 0xffffffffffffff7c) = var_78;
                            r0 = (r12)(@class(NSString), @selector(stringWithFormat:), r2, var_84);
                            r0 = [r0 retain];
                            r3 = *((r7 - 0x100) + 0xffffffffffffff7c);
                            *((r7 - 0x100) + 0xffffffffffffff78) = r0;
                            r0 = r3;
                            *((r7 - 0x100) + 0xffffffffffffff74) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff78);
                            r3 = *((r7 - 0x100) + 0xffffffffffffff74);
                            r0 = (r3)(r0, @selector(stringByAppendingString:), r2, r3);
                            r7 = r7;
                            r0 = [r0 retain];
                            *((r7 - 0x100) + 0xffffffffffffff70) = r0;
                            *((r7 - 0x100) + 0xffffffffffffff6c) = _objc_msgSend;
                            r2 = *((r7 - 0x100) + 0xffffffffffffff6c);
                            r1 = var_78;
                            var_78 = (r2)(r0, @selector(mutableCopy), r2, r0);
                            [r1 release];
                            r0 = *((r7 - 0x100) + 0xffffffffffffff70);
                            [r0 release];
                            r0 = *((r7 - 0x100) + 0xffffffffffffff78);
                            [r0 release];
                    }
                    objc_storeStrong(r7 - 0x84, 0x0);
                    var_80 = var_80 + 0x1;
            } while (true);
            objc_storeStrong(r7 - 0x70, var_78);
            objc_storeStrong(r7 - 0x78, 0x0);
            objc_storeStrong(r7 - 0x74, 0x0);
    }
    *((r7 - 0x100) + 0xffffffffffffff68) = [var_70 retain];
    objc_storeStrong(r7 - 0x70, 0x0);
    objc_storeStrong(r7 - 0x60, 0x0);
    objc_storeStrong(r7 - 0x3c, 0x0);
    objc_storeStrong(r7 - 0x30, 0x0);
    objc_storeStrong(r7 - 0x2c, 0x0);
    r0 = *((r7 - 0x100) + 0xffffffffffffff68);
    r0 = [r0 autorelease];
    r1 = *___stack_chk_guard;
    *((r7 - 0x100) + 0xffffffffffffff64) = r0;
    if (r1 == var_20) {
            r0 = *((r7 - 0x100) + 0xffffffffffffff64);
    }
    else {
            r0 = __stack_chk_fail();
    }
    return r0;
}

代码很长,看起来很头疼,其实也没必要一行一行去看懂。利用CCCrypt的各个参数进行对比分析,大概的加密逻辑也是很容易推测出来的。

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)
  • op,操作类型,由变量var_34控制,即doCipher:key:context最后一个参数,为0时,进行加密操作
    enum { kCCEncrypt = 0, kCCDecrypt, };

  • alg,加密算法,伪代码为0x1,即kCCAlgorithmDES,为DES加密方式

enum {
    kCCAlgorithmAES128 = 0,
    kCCAlgorithmAES = 0,
    kCCAlgorithmDES,
    kCCAlgorithm3DES,       
    kCCAlgorithmCAST,       
    kCCAlgorithmRC4,
    kCCAlgorithmRC2,   
    kCCAlgorithmBlowfish    
};
typedef uint32_t CCAlgorithm;
  • options,代码对应变量r12 = 0x1,对应枚举为kCCOptionPKCS7Padding
*(var_34 + 0xffffffffffffffec) = 0x1;
r12 = *(var_34 + 0xffffffffffffffec)
  • key,密钥,对应到doCipher:key:context的第2个参数,需要转化成char *型,在[DESUtil encode]中,可以找到相应的密钥02****5a(8位,属于敏感信息,中间4位隐藏处理),伪代码 对应 var_F0的值:
[[DESUtil doCipher:r0 key:@"02****5a" context:stack[2033], stack[2034], stack[2035]] retain]


var_F0 = [objc_retainAutorelease(var_60) bytes]
  • keyLength,密钥长度,伪代码var_F8 = [var_60 length]
  • iv,加密向量,代码对应(r7 - 0x100)的值,与var_F0一致
*(r7 - 0x100) = [objc_retainAutorelease(var_60) bytes];
  • dataIn,需要加密的内容,即doCipher:key:context的1个参数
  • dataInLength,加密内容的长度
  • dataOut,加密后的内容
  • dataOutAvailable,加密后内容长度
var_68 = [0x0 length] + 0x8 & 0xfffffff8;
  • dataOutMoved,输出值,不用关心

至此,明确了函数使用DES加密方式,并且加密向量与密钥相同,继续住下分析。

2.2 DES结果处理

  • 先将加密后的内容转化成NSData值:
r0 = (*((r7 - 0x100) + 0xffffffffffffffc8))(@class(NSData), @selector(dataWithBytes:length:), var_64, 0x0)
  • 按字节读取NSData值,并转化成十六进制格式的字符串r2:
 r3 = *((r7 - 0x100) + 0xffffffffffffffb4);
 *((r7 - 0x100) + 0xffffffffffffffb0) = var_80;
 if (*((r7 - 0x100) + 0xffffffffffffffb0) >= (r3)(var_74, @selector(length), var_80, r3)) {
        break;
   }
  s0 = *@"%x";
  *((r7 - 0x100) + 0xffffffffffffffac) = @"%x";
  *((r7 - 0x100) + 0xffffffffffffffa8) = _objc_msgSend;
  r0 = (*((r7 - 0x100) + 0xffffffffffffffa8))(@class(NSString), @selector(stringWithFormat:), *((r7 - 0x100) + 0xffffffffffffffac), s0 & 0xff);
  r7 = r7;
  var_84 = [r0 retain];
  *((r7 - 0x100) + 0xffffffffffffffa4) = _objc_msgSend;
  r2 = *((r7 - 0x100) + 0xffffffffffffffa4);
  • 判断r2长度,如果长度为1,则在前面补0;这也是为什么在解密代码中,会有高16位、低16位判断的原因
if ((r2)(var_84, @selector(length), r2) == 0x1) {
   r2 = *(("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics" | 0x2c0000) + 0x4bd9c);
   *((r7 - 0x100) + 0xffffffffffffffa0) = @"0%@";
   *((r7 - 0x100) + 0xffffffffffffff9c) = _objc_msgSend;
   r1 = r2;
   r2 = *((r7 - 0x100) + 0xffffffffffffffa0);
   r12 = *((r7 - 0x100) + 0xffffffffffffff9c);
  ...
   r0 = *((r7 - 0x100) + 0xffffffffffffff94);
  [r0 release];
}
  • 其实整个循环,就是将NSData的值按字节转化成十六进制格式的字符串:
    比如字符串“a”,经过上述DES加密后,得到的NSData值为<ed531e0b 195ec5b7>,转化成字符串后为 ed531e0b195ec5b7,即为最终加密的结果。而通常AES/DES加密后,会将结果直接转换成Base64。

为了测试方便,在NSString增加了一个DES的类别,实现与伪代码相似的功能,具体见附录。

3 ServiceUtil

网络请求部分都在ServiceUtil里面,设置AFNetWorking参数、HTTP请求头等。

3.1 请求头设置

看伪代码,只是简单设置了AcceptUser-agentAccept-LanguageContent-TypeContent-Length这几个值,并没有做过多的校验,通过WEB模拟发送POST请求,应该也能通过。

void -[ServiceUtil setRequestHead:len:](void * self, void * _cmd, void * arg2, void * arg3) {
    objc_storeStrong((sp - 0x54) + 0x40, arg2);
    objc_storeStrong((sp - 0x54) + 0x3c, arg3);
    [0x0 addValue:@"application/json" forHTTPHeaderField:@"Accept", stack[2027], stack[2028], stack[2029]];
    [0x0 addValue:@"Mozilla/5.0" forHTTPHeaderField:@"User-agent", stack[2027], stack[2028], stack[2029]];
    [0x0 addValue:@"ZH-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4" forHTTPHeaderField:@"Accept-Language", stack[2027], stack[2028], stack[2029]];
    [0x0 addValue:@"application/json" forHTTPHeaderField:@"Content-Type", stack[2027], stack[2028], r2];
    [0x0 addValue:0x0 forHTTPHeaderField:@"Content-Length", r1, @"Content-Length"];
    objc_storeStrong((sp - 0x54) + 0x3c, 0x0);
    objc_storeStrong((sp - 0x54) + 0x40, 0x0);
    return;
}

3.2 HTTP端口

代码内部有一个HTTP的端口:http://app.xxxxtech.com:8080,结合登录url,可以推测,其他的请求都是用特定的功能字符串拼接起来的,用web模拟,发现8080端口也是有效的。
http://app.xxxxtech.com:8080/GetAndroidDataService.svc/xxxx
https://app.xxxxtech.com/GetAndroidDataService.svc/xxxx

void * -[ServiceUtil http](void * self, void * _cmd) {
    stack[2043] = r4;
    r7 = (sp - 0x14) + 0xc;
    *((sp - 0x14) + 0xfffffffffffffffc) = r8;
    sp = (sp - 0x14) + 0xfffffffffffffffc - 0x40;
    stack[2042] = self;
    if (stack[2042]->_http == 0x0) {
            r0 = (*@"%@%@%@")(@class(NSString), @selector(stringWithFormat:), @"%@%@%@", @"http://", @"app.xxxxtech.com:8080", @"/");
            r0 = [r0 retain];
            stack[2034] = r0;
            r0 = (*r0)(@class(NSMutableString), @selector(stringWithFormat:), @"%@%@%@", stack[2034], @"GetAndroidDataService.svc", @"/");

    ...
    }
    r0 = stack[2042]->_http;
    r0 = loc_239340(r0, *0x31aa04);
    return r0;
}

4 安全检验

登入App后,试了其他几个请求,发现请求参数、请求头并没有登录返回的FToken信息,难道登录只是进入App的壳子,后续的请求根本不需要检验?找一个接口一试究竟。

查询打卡时间的接口,参数只有工号是可变的:

https://app.xxxxtech.com/GetAndroidDataService.svc/GetCheckStatusData

{
  "FOSVersion" : "10.3.2",
  "FMobileType" : "IOS",
  "FAppVersion" : "v4.0.2.Basics (51)",
  "FModuleId" : "3093",
  "FItemNumber" : "*****"
}

FItemNumber修改为其他的5位数,并对参数加密,得到请求参数(敏感信息隐藏处理):

{"jsonData":"0e10de0e07c7f12758af1e31ffdea690c040bb8ecab59985f118ebfbb6d0500cafaa48ff3194a97be6eb6053ec6c6206db03e151be4b528d78db3becbcb1629fc29d0e049cff1a27e5584d684d8b78e7a925a47801cd1511d6a98c7b1c33debbe446eed7fe7674987e52e6b64bd3f73fb9ebfb7a986b5c16537..."}

使用web发送请求,可以得到正常返回:

    "FID": "",
    "IsSuccess": true,
    "Result": "{\"FStatus\":0,\"FAttendId\":0,\"FCheckInTime\":\"**:**\",\"FCheckOutTime\":\"22:04\"}",
    "ResultCode": 200
}

再试其他接口,也是可以直接通过的,可见除了登录协议外,其他协议都没有做安全性校验。

5 总结

虽然App是内部用的,但有几点还是可以再提高一下的:

增加请求头、安全检验
增加DES加密的复杂度
关闭8080端口
关键几处函数进行代码混淆,反正不需要AppStore审核

附录:加解密代码

#import "NSString+DES.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>

@implementation NSString (DES)

- (NSData *)jm_hexStringConvertToBytesData
{
    //异常字符串
    if (self.length % 2 != 0) {
        return nil;
    }
    
    Byte bytes[1024*3] = {0};
    int bytesIndex = 0;
    
    for(int i = 0; i < [self length]; i++)
    {
        int int_char;  /// 两位16进制数转化后的10进制数
        
        unichar hex_charUpper = [self characterAtIndex:i]; ///两位16进制数中的第一位(高位*16)
        int int_charUpper;
        if(hex_charUpper >= '0' && hex_charUpper <='9') {
            int_charUpper = (hex_charUpper - 48 ) * 16;   // 0 的Ascll - 48
        } else if(hex_charUpper >= 'A' && hex_charUpper <= 'F') {
            int_charUpper = (hex_charUpper - 55 ) * 16; /// A 的Ascll - 65
        } else {
            int_charUpper = (hex_charUpper - 87 ) * 16; // a 的Ascll - 97
        }
        
        i++;
        
        unichar hex_charLower = [self characterAtIndex:i]; ///两位16进制数中的第二位(低位)
        int int_charLower;
        if(hex_charLower >= '0' && hex_charLower <= '9') {
            int_charLower = (hex_charLower - 48); /// 0 的Ascll - 48
        } else if(hex_charUpper >= 'A' && hex_charUpper <='F') {
            int_charLower = (hex_charLower - 55); ///  A 的Ascll - 65
        } else {
            int_charLower = hex_charLower - 87; /// a 的Ascll - 97
        }
        
        int_char = int_charUpper + int_charLower;
        bytes[bytesIndex] = int_char;  ///将转化后的数放入Byte数组里
        bytesIndex++;
    }
    
    NSUInteger dataLength = self.length / 2;
    NSData *data = [[NSData alloc] initWithBytes:bytes length:dataLength];
    return data;
}

- (NSString *)jm_urlDecode {
    NSString *decodedString = [self stringByRemovingPercentEncoding];
    return decodedString;
}

- (NSString *)jm_urlEncode {
    NSString *encodedString = [self stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"!*'();:@&=+$,/?%#[]"]];
    return encodedString;
}

- (NSString *)jm_encryptUseDESByKey:(NSString *)key iv:(NSString *)iv
{
    NSString *ciphertext;
    NSString *encode = [self jm_urlEncode];
//    NSLog(@"%s encode::%@", __func__, encode);
    
    NSData *data = [encode dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger dataLength = data.length;
    NSUInteger bufferLength = 1024;
    unsigned char buffer[bufferLength];
    memset(buffer, 0, sizeof(char));
    
    size_t numBytesEncrypted = 0;

    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmDES,
                                          kCCOptionPKCS7Padding,
                                          [key UTF8String],
                                          kCCKeySizeDES,
                                          [iv UTF8String] , //iv向量
                                          [data bytes],
                                          dataLength,
                                          buffer,
                                          bufferLength,
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
        //NSLog(@"%s buffer::%s", __func__, buffer);
        //NSLog(@"%s data::%@", __func__, data);
    
        ciphertext = @"";
        for (int index = 0; index < data.length; index++) {
            char byte;
            [data getBytes:&byte range:NSMakeRange(index, 1)];
            NSString *text = [NSString stringWithFormat:@"%x", byte&0xff];
            
            //不足两位,前面补0
            if([text length] == 1) {
                text = [NSString stringWithFormat:@"0%@", text];
            }
            
            ciphertext = [ciphertext stringByAppendingString:text];
        }
    }
    
    NSLog(@"%s encryptText::%@", __func__, ciphertext);
    return ciphertext;
}

- (NSString *)jm_decryptUseDesByKey:(NSString *)key iv:(NSString *)iv
{
    NSString *decryptText;
    
    NSData *encryptData = [self jm_hexStringConvertToBytesData];
    const char *textBytes = [encryptData bytes];
    
    NSUInteger dataLength = encryptData.length;
    NSUInteger bufferLength = dataLength + 0x8 & 0xfffffff8;
    unsigned char buffer[bufferLength];
    memset(buffer, 0, sizeof(char));

    size_t numBytesEncrypted = 0;
    
    //将encryptText转化为bytes
    CCCryptorStatus decryptStatus = CCCrypt(kCCDecrypt,
                                            kCCAlgorithmDES,
                                            kCCOptionPKCS7Padding,
                                            [key UTF8String],
                                            kCCKeySizeDES,
                                            [iv UTF8String] , //iv向量
                                            textBytes,
                                            dataLength,
                                            buffer,
                                            bufferLength,
                                            &numBytesEncrypted);
    if (decryptStatus == kCCSuccess ) {
        
        NSLog(@"%s buffer::%s", __func__, buffer);
        NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
        
        NSLog(@"%s data::%@", __func__, data);
        
        decryptText = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%s decryptText::%@", __func__, decryptText);
        
        decryptText = [decryptText jm_urlDecode];
        NSLog(@"%s decodeUrl::%@", __func__, decryptText);
        
    }
    
    return decryptText;
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,320评论 8 265
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 夜太浓 它一定是硌疼了你匆匆赶路的高跟鞋 匆匆 它们如玫瑰一样高贵的红 是夜色中的王后 而阳光 是它们背弃的白裙 ...
    吾乃某山某某某阅读 139评论 0 0
  • 单曲循环了三天的歌 今天终于去看了电影 有点浮夸 可是感情很细腻 很细微 很纯真 很好看 还有 今天下了初...
    MaoMaoMei阅读 311评论 5 0
  • 今年的生日恰逢周末,又赶上了丽水市图书馆的朗读者之夜。吃过晚饭,我骑着小毛驴穿过半个城市去图书馆里参加此次活动。一...
    丁蜻阅读 527评论 0 0