产品需求是把积分查询以页面的形式展示,本来以为给一个url直接打开webview就行了,实际上远比想的要复杂,为了安全。
1,url是后台接口请求成功了才返回的。接口要上送sign和data两个参数,其中sign就是签名,通过一系列业务参数和请求头参数排序偏接,最后md5.
NSString *str=[NSString stringWithFormat:@"%@%@%@%@%@%@%@",@"F49E993869A212F3F505ED53",@"102",@"102",timeStt,@"1.1.1",@"102",vsTr];
NSString *sign=[tool.md5 getMD5StringWithString:str] ;
data就是请求头参数和业务参数一起组合成一个字典,然后3des加密上送。
{
sign = "3ef8ca5186d7b301479bae31cb0c7de9",
data = "密文l",
}
差不多是这个意思,结果后台一直取值参数为空,sign和data取值都为nil,又出现以前的问题了,下班之前阿斌顺便说了句 form提交可以成功,按之前钱包后台的json恐怕不行,要不你试试
NSString *postString = [self getFormDataString:para];
NSData *data = [postString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
[request setValue:@"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
都已经这么设置了 ,但是还是反复收不到值。后来干脆下班了,走的时候跟楠哥打了个电话描述,楠哥人真的很好,看到别人有困难,发自内心尽自己能力帮忙,我描述完,楠哥答应了,楠哥一直是让人觉得有安全感很踏实的人。睡觉的时候还在想,该做的都做了,怎么就是收不到,后来想到只有data对应的value才需要加密,但是我感觉把整体字典都转data还加密了。觉得第二天自己再试试自己的猜想,要不然楠哥从头看起,或许更乱。
{
sign = "3ef8ca5186d7b301479bae31cb0c7de9",
data = "密文l",
}
所以此时后台根据原先的两个key sign和data是怎么都取不到值的。因为key已经被我无意中加密了,为了快省事,复制以前的代码和经验。后来只好改过来第二天试试。后面的结果过不其然,验证了自己的猜想。人有时候总还算有些灵感的。或者说不到这个点,就是发现不了。
// [request setHTTPBody:[[self encodeXML:data withKey:@"F49E993869A212F3F505ED53" application:nil] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:data];
}
para指的就是这个字典 ,字段过多,就不展示了,大概7个key-value吧,组成个字典装下
//先转data
NSData *data = [NSJSONSerialization dataWithJSONObject:para options:NSJSONWritingPrettyPrinted error:nil];
//data转成字符串 然后3des加密 (因为这里需要传字符串,其实是字典变成data,然后data又变成字符串了,字典变成字符串很神奇,data竟然是桥梁)
、//TripleDES 加密方法 得到密文字符串
NSString * encryptionData=[NSString TripleDES:[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding] encryptOrDecrypt:kCCEncrypt];
//拿到密文,组装成字典上送,这里做到两个key都没有加密,第二个value data对应的值才加密。
NSMutableDictionary *idic=[[NSMutableDictionary alloc] initWithDictionary:@{@"sign":sign,@"data":encryptionData}];
[[YFCardCoponRequest sharedInstance] requestWithDictionary:idic hasLoading:@"" completion:^(BOOL success, id responseData) {
if (responseData) {
}
}];
此时成功了一半,然后又遇到第二个问题,加密过去的data值 ,后台解不了。说有代码例子。
public static String encode(String data, String key){
String result = "";
try {
if (data == null || data.length() == 0) {
throw new RuntimeException();
}
DESedeKeySpec dks = new DESedeKeySpec(key.getBytes("UTF-8"));
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("DESede");
SecretKey securekey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, securekey);
byte[] retByte = cipher.doFinal(Base64.encode(data.getBytes()).getBytes());
result = Base64.encode(retByte);
result = result.replace('+', '_').replace('=', '%')
.replace('/', '*');
} catch (Exception e) {
e.printStackTrace();
result = "";
}
return result;
}
本人做iOS的只能看一个大概,于是没有仔细细看。就盯着DESede/ECB/PKCS5Padding ,然后网上找个匹配的。后来找了四五套3des包括des的 类去匹配,结果总是解不开。问了下好友来瑞松。说你最好把java发过来的加密方法,随便找个串加密了对比下,跟你加出来是是不是一样的。然后把java的加解密类导入eclipse,果真不一样,
然后开始仔细阅读和理解java的这段加密过程,在对比我找的几套加密,一步步参照,发现最后
result = Base64.encode(retByte);
result = result.replace('+', '_').replace('=', '%').replace('/', '*');//这是重点,本来已经拿到result,还有字符替换。原来他们也是网上找的java加密类,只是他们也担心安全就在结尾处拿到加密串了,替换某些字符,然后解密的时候也跟着替换了。网上找的现成了,自己还改了,那我网上找的对的也匹配不上啊。。。。
明白这里我也跟着在方法里面加这些所谓的替换。跟旭东一起研究,因为他是原先做iOS,然后自学java转过去的。可惜的是,到后来加密出来的结果还是对不上,可喜的是长度一致!到这里真的没有招了,网上找的人家写好的加密类,又是对着java的加密最后字符替换。自己写,底层的不太明白,后台说正好商城的同事有现成的,要不过来试试,怎么才说!安卓直接现成的,拿过去都不用调,所以做安卓还是很有优势的,语言一致,不存在这些问题!
商城的同事后来发过来一个类,放在项目里面直接用,是成功的,以为他们之前已经调过了。得到前面的验证。发现和龙哥发过来的方法看上去几乎一样,
还有特别的说明,这是个大神级别的,实力不容怀疑的。在我们看来,他出现了就有安全,就没有解决不了的问题。手机银行号称。
正好替换字符,也正是他发的这个方法上改写的。他都说了 ,好多项目都是用的这个。连银行都用的这个,应该是加解密写法肯定没问题了。
对比了下商城同事发过来的 ,有两点不一样,其中一个影响很大,一个几乎没有影响,
一是
Byte iv[] = {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF}; //商城的同事发过来的这行是注释的
ccStatus = CCCrypt(encryptOrDecrypt,
kCCAlgorithm3DES,
kCCOptionPKCS7Padding|kCCOptionECBMode,
vkey,
kCCKeySize3DES,
iv, //这里他们就传nil
vplainText,
plainTextBufferSize,
(void *)bufferPtr,
bufferPtrSize,
&movedBytes);
二是,也是这段代码 ,设置模式商城的同事是 kCCOptionPKCS7Padding|kCCOptionECBMode,
龙哥给的是kCCOptionPKCS7Padding。
然后问题就在这里了,后来我们得到的是长度一致,内容不一样。第一个 Byte iv[] 这个传也行,传nil也不会有影响,|kCCOptionECBMode 这个是必须要加的,以防止万一,结果我们的后台对调就遇到了,他们的代码里面Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
第一个说的是3des方式加密 第二个是加密模式ECB 第三个是长度(具体不明白,有的说5位的 128字节一段加密,当然还有256位的)
没有加,导致模式不是ECB的 ,而且长度和PKCS5Padding么有一个有效的匹配,iOS里面这个没得选,只有个7pading的
开始怀疑 kCCOptionECBMode 这个的指定是任何长度的匹配和模式的指定。不过只是怀疑,最后加上这个就加密结果一样。后面的流程很顺,前面准备的数据也没错。
补充一点的就是加签和验签,加签方式前面说过了,偏接字符串,md5,把加密串给后台就行。验签是这样,后台也会返回个sign对应字段有个值,我这边需要偏接下返回来的其他的业务参数,做一下md5去比较,如果一致,验签通过。防止数据被串改。其实按照斌仔说的 ,url都直接返回了,打开就行了。呵呵
NSString *str=[NSString stringWithFormat:@"%@%@%@",@"F49E993869A212F3F505ED53",dic[@"respCode"],dic[@"result"][@"url"]];
NSString *sign=[tool.md5 getMD5StringWithString:str] ;
if ([signResp isEqual:sign]) { //指的是后台接口成功返回的
completion(YES,dic);
}
说了一大堆,也行只能自己看懂,不过确实印象很深,康斌到后来问了说学到不少东西了吧。确实是,加密的过程有更深的理解。第一个坑是自己挖的,没有人讨论的话,有时候很难发现,总以为是java取值,转流的问题。深刻的记录一次!