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:
登錄一般只是檢驗密碼,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
類進行加密:
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 請求頭設置
看偽代碼,只是簡單設置了Accept
、User-agent
、Accept-Language
、Content-Type
、Content-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