最近帮别人看了一个使用HMAC-SHA1 进行加密授权快盘的一个认证,经过测试,第一步获取 oAuth-token 就会出现错误,错误原因是,签名失败,也就是说我们的签名某个地方错误了,签名这个地方看着好做,其实一步走错,就会全部错哦,我们就完全跳进坑了
一、 下面我们看一下,第一步我们要怎么去接入快盘
- 点击进入快盘开发者中心
- 我们进行一个账号注册,添加一个应用 后然后获取对应的
auth_consumer_key
和consumer_secret
- 点击进入开发者文档一栏,这些文档可以在使用的时候慢慢研究
- 点击 左侧的 OAuth 协议 【RFC58493.4 HMAC-SHA1】最下面的签名生成算法
我们会看到如下几个重要的地方
其实这个加密规则还算简单,就是encode 稍微有点麻烦,我们感觉快盘的其实只是提供了一些API接口,并没有什么SDK,所以我们要完全按照文档提示的操作步骤走
二、签名算法的生成
- 我们先了解一下官方API的签名,官方提供给我们的是Python 写的实例,有些不懂的似乎看的不是太懂。我们看下官方提供的思路
假设服务器地址为 openapi.kuaipan.cn,现在需要向
http://openapi.kuaipan.cn/1/fileops/create_folder 用GET方法发
出请求,请求参数 (parameters) 如下:
{
'oauth_version': '1.0',
'oauth_token': 'fa361a4a1dfc4a739869020e586582f9',
'oauth_signature_method': 'HMAC-SHA1',
'oauth_nonce': '58456623',
'oauth_timestamp': 1328881571,
'oauth_consumer_key': '79a7578ce6cf4a6fa27dbf30c6324df4',
'path': '/[email protected]',
'root': 'kuaipan'
}
//Python 签名加密格式:
http_method + "&" +
url_encode( base_uri ) + "&" +
url_encode(
“&”.join(
sort( [url_encode ( k ) + "=" +url_encode ( v ) for k, v in paramesters.items() ]
)
)
我们将加密规则翻译过来看一下iOS中我们要如何写(拼接字符串参数) 步骤如下:(下面步骤都是拼接关系,我们进行拆分)
- HTTPMethod:
GET
大写 - 地址符号:
&
- url_encode:
base_uri utf8string]
对基地址进行URL编码 - join 前面的
&
:此处表示将一个数组拼接起来的意思,紧跟后面的sort 函数结果
,并不是 此处用&
去连接 - sort : 按照官方给予的解释就是将参数按照 字典的ASCII 码进行Key值排序,例子给出的是升序排列组合, Python 所谓的字典就是Map 集合,OC对应的就是NSDictionary -字典
- sort函数内部: 里面的for 循环代表是循环遍历字典,并且每个键值对 都进行一次URL encode。循环之后的键值对经过
&
进行连接到一起 - 最终我们将4、5、6的最终拼接完成的字符串,再次经过一次URL ,encdoe ,就得到官方所说的原串的编码拼接完毕。
我们看一下官方生成的签名格式:
GET&http%3A%2F%2Fopenapi.kuaipan.cn%2F1%2Ffileops%2Fcre
ate_folder&oauth_consumer_key%3D79a7578ce6cf4a6fa27
dbf30c6324df4%26oauth_nonce%3D58456623%26oauth_signat
ure_method%3DHMAC-
SHA1%26oauth_timestamp%3D1328881571
%26oauth_token%3Dfa361a4a1dfc4a739869020e586582f9%26
oauth_version%3D1.0%26path%3D%252Ftest%2540kingsoft.co
m%26root%3Dkuaipan
原串编码组合完毕形式如上所示:
- 原串编码完毕,我们使用HMAC-SHA1进行秘钥加密:
- 生成签名加密的秘钥,格式如下:
然后生成签名加密的密钥(记得有个&),注意假如没有 oauth_token的话,"&"之后的部分是不用包含的:
c7ed87c12e784e48983e3bcdc6889dad&0183ce137e4d4170b2ac19d3a9fda677
- 使用密钥通过HMAC-SHA1算法签名字符基串,生成签名(先生成数字签名,然后再用base64 encode):
pa7Fuh9GQnsPc+Lcn+Qu6G7LVEU=
- 最后把urlencode后的签名作为oauth_signature的值,向连接发出请求:
url_encode(auth_signature) 进行最后一次编码,现在就可以直接发送请求了:
通过终端或者通过官方提供的平台都可以测试:
终端命令:
curl –k
"http://openapi.kuaipan.cn/1/fileops/create_folder?oauth_version=1.0&oauth_signature=pa7Fuh9GQnsPc%2BLcn
%2BQu6G7LVEU%3D&oauth_token=fa361a4a1dfc4a739869020e586582f9&oauth_signature_method=HMAC-SHA1&oauth_nonc
e=58456623&oauth_timestamp=1328881571&path=%2Ftest%40kingsoft.com&oauth_consumer_key=79a7578ce6cf4a6fa27
dbf30c6324df4&root=kuaipan"
三、iOS中如何去操作这些加密规则?
我们直接代码接入测试:
- 准备好申请的Key 和 Secret ,我们这里只做第一步的签名认证:
获取requestToken
baseUri :
static NSString * baseUrl = @"https://openapi.kuaipan.cn/open/requestToken";
参数准备:
NSDictionary * prama = @{@"oauth_consumer_key":@"xcVaIWFCPRTVabGH",
@"oauth_signature_method":@"HMAC-SHA1",
@"oauth_timestamp":[self dateString],
@"oauth_nonce":[self onceString],
@"oauth_version":@"1.0",
};
涉及到的两个方法 dateString
和 onceString
- (NSString *)dateString{
NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval a=[dat timeIntervalSince1970]*1000;
NSString *timeString = [NSString stringWithFormat:@"%.f", a];
NSRange rang = {0,10};
NSString *subString = [timeString substringWithRange:rang];
return subString;
}
- (NSString *)onceString{
NSString *string = [[NSString alloc]init];
for (int i = 0; i<8; i++) {
int number = arc4random() % 25;
if (number < 10) {
int figure = (arc4random() % 26) + 97;
char character = figure;
NSString *tempString = [NSString stringWithFormat:@"%c", character];
string = [string stringByAppendingString:tempString];
if (figure%2==0) {
[string uppercaseString];
}
}else {
int figure = arc4random() % 10;
NSString *tempString = [NSString stringWithFormat:@"%d", figure];
string = [string stringByAppendingString:tempString];
}
}
return string;
}
我们所用到的URL——Encode方法如下
- (NSString *)encodeToPercentEscapeString: (NSString *) input
{
NSString*
outputStr = (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL, /* allocator */
(__bridge CFStringRef)input,
NULL, /* charactersToLeaveUnescaped */
(CFStringRef)@"!*'();:@&=+$,/?%#[]",
kCFStringEncodingUTF8);
return outputStr;
}
HMAC-SHA1 签名之后,使用Base64编码
签名格式: HMAC-SHA1(原串,秘钥) -> Base64 编码
使用HMAC,时需要导入一下两个类库
#import
#import
+ (NSString *)hmac_sha1:(NSString *)key text:(NSString *)text{
const char *cKey = [key cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [text cStringUsingEncoding:NSUTF8StringEncoding];
char cHMAC[CC_SHA1_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:CC_SHA1_DIGEST_LENGTH];
NSString *hash = [HMAC base64Encoding];//base64Encoding函数在NSData+Base64中定义(NSData+Base64网上有很多资源)
return hash;
}
- 下面我们进行对参数进行排序:代码如下:
NSArray * keyArray = [prama allKeys];
NSArray * sortArray = [keyArray sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2 options:NSLiteralSearch];
}];
- 字典排序完毕,我们要进行组合,并对其采用URL编码:
NSMutableString * appendString = [NSMutableString string];
[appendString appendString:@"GET&"];
[appendString appendString:[self encodeToPercentEscapeString:baseUrl]];
[appendString appendString:@"&"];
NSMutableArray * tempArray = [NSMutableArray array];
for (NSString * keys in sortArray) {
[tempArray addObject:[NSString stringWithFormat:@"%@=%@",[self encodeToPercentEscapeString:keys],[self encodeToPercentEscapeString:prama[keys]]]];
}
NSString * comments = [tempArray componentsJoinedByString:@"&"];
NSLog(@"com = %@",comments);
[appendString appendString:[self encodeToPercentEscapeString:comments]];
- 下面进行HMAC-SHA1签名:
NSString * macsha = [self.class hmac_sha1:@"申请应用所获得consumer_secret&" text:appendString];
NSString * praurl = [NSString stringWithFormat:@"oauth_signature=%@&oauth_consumer_key=%@&oauth_nonce=%@&oauth_signature_method=%@&oauth_timestamp=%@&oauth_version=%@",[self encodeToPercentEscapeString:macsha],prama[@"oauth_consumer_key"],prama[@"oauth_nonce"],prama[@"oauth_signature_method"],prama[@"oauth_timestamp"],prama[@"oauth_version"]];
// NSLog(@"prama = %@",praurl);
NSString * replceurl = [NSString stringWithFormat:@"%@?%@",baseUrl,praurl];
- 签名组合完毕,进行如下请求验证获取第一次的Token:
NSURL * url = [NSURL URLWithString:replceurl];
NSLog(@"url = %@",url);
NSMutableURLRequest * requestUrl = [NSMutableURLRequest requestWithURL:url];
[requestUrl setHTTPMethod:@"GET"];
NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask * task = [session dataTaskWithRequest:requestUrl completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
NSLog(@"有数据");
NSString * string = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"string1 = %@",string);
}else{
NSString * string = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"string2 = %@",string);
}
NSLog(@"responce = %d",[(NSHTTPURLResponse *)response statusCode]);
if (error) {
NSLog(@"error = %@",[error localizedFailureReason]);
}
}];
[task resume];
如果在请求中返回如下 code :
如图,选中的部分基本上是签名错误会返回的,这个问题看着很简单,因为如果不是按照规定的编码顺序,这个问题或者你要看上一天两天了,我在帮他调试的时候,最后一步的因为一个参数未编码,导致多浪费了半天时间,真的是自己挖坑哦。
总结:
以上是快盘接入APP进行的一个签名授权验证, 后续一直按照如上方法,一直到获取到accessToken 为止,如果还有不明白的,请留言!
如果以上有写错的地方,还望指出,给一些需要的人一些帮助。