接口结构
接口一律采用https , 如:
https://api.xxx.cn/version/controller/action
请求参数
字段名 | 变量名 | 类型 | 描述 |
---|---|---|---|
业务动作 | action | String(32) | 操作, 如: add |
业务内容 | content | String(32) | 业务数据, json对象 |
控制模块 | controller | String(32) | 业务模块, 如: user |
随机字符串 | nonceStr | String(32) | 随机字符串, 不长于32位。推荐随机数生成算法 |
签名 | sign | String(32) | 签名,详见签名生成算法 |
签名类型 | sign_type | String(32) | 签名类型, 如HMAC-SHA256或MD5, 默认为MD5 |
服务Id | serviceId | String(32) | 用于服务器快速定位用户的临时数据, 在app第一次注册的时候获取 |
返回结果
字段名 | 变量名 | 类型 | 描述 |
---|---|---|---|
业务动作 | action | String(32) | 操作, 如: add |
业务内容 | content | String(32) | 业务数据, json对象 |
控制模块 | controller | String(32) | 业务模块, 如: user |
返回状态码 | returnCode | String(16) | SUCCESS/FAIL 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断 |
返回信息 | returnMsg | String(128) | 返回信息,如非空,为错误原因签名失败参数格式校验错误 |
签名算法
第一步
将所有的数据(剔除签名字段), 按照key值的ASCII码从小到大排序, 直接输出成json字符串stringA.
- 参数名ASCII码从小到大排序(字典序);
- 参数名区分大小写;
第二步
在stringA的后面拼接和服务器约定的密钥, 使用密码. 得到stringATemp, 再对stringATemp进行MD5运算, 将得到的字符串全部转换成大写, 得到sign的值
假设待提交的数据:
{
"action": "add",
"nonceStr": "ibuaiVcKdpRxkhJA",
"content": {
"userName": "111",
"passWord":"123"
},
"controller": "user",
}
第一步之后得到字符串
stringA = {"action": "add","content": {"userName": "111","passWord":"123" },"controller": "user","nonceStr":"ibuaiVcKdpRxkhJA"}
第二步拼接key
stringATemp = stringA + password
sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7"
最终得到的发出去的数据为:
{
"action": "add",
"nonceStr": "ibuaiVcKdpRxkhJA",
"content": {
"userName": "111",
"passWord":"123"
},
"controller": "user",
"sign": "9A0A8659F005D6984697E2CA0A9CF3B7"
}
key值的约定
key值的约定
由于服务器的密码存储一般是md5(password + salt)
记为A
值, 但是在login的时候, 这个A
值会在网络层中传输一次, 如果攻击者劫持了login接口, 那么就获取了key值,
所以建议在服务器表中除了A
, 再多存储一个md5(password + salt2)
记为B
,
这个B
值作为客户的与服务器约定密码key值,
即传输报文的salt.
随机数
主要保证签名不可预测
java
public static String random() {
return UUID.randomUUID().toString().replace("-", "");
}
oc
+ (NSString *)random {
return [[[NSUUID UUID] UUIDString] stringByReplacingOccurrencesOfString:@"-" withString:@""];
}
实现
Android
实现算法的第一步和第二步 (使用fastjson
, 可自动实现key值排序)
/**
* 计算jsonObject的json值, 并添加key
*
* @param jsonObject
* @param uuid, 和服务器约定的密钥, 可用password代替
* @return
*/
public static String encode(JSONObject jsonObject, String uuid) {
StringBuilder sb = new StringBuilder();
sb.append(jsonObject.toJSONString());
sb.append(uuid);
String md5 = MD5.stringMD5(sb.toString());
return md5;
}
调用
/**
* 对发送的json加密
*
* @param jsonObject
* @param uuid
* @param serviceId
* @return
*/
public static JSONObject encryptJSON(JSONObject jsonObject, String uuid, String serviceId) {
jsonObject.put("sign", encode(jsonObject, uuid));
jsonObject.put("serviceId", serviceId);
return jsonObject;
}
校验服务器返回数据
/**
* 检查服务器返回json是否正确
*
* @param jsonObject
* @param uuid, 和服务器约定的密钥, 一般可用用户密码
* @return
*/
public static boolean checkEncrypt(JSONObject jsonObject, String uuid) {
String checkNumService = jsonObject.getString("sign");
jsonObject.remove("sign");
return checkNumService != null && checkNumService.equals(encode(jsonObject, uuid));
}
iOS
工具方法
// 获取随机数
+ (NSString *)random {
return [[[NSUUID UUID] UUIDString] stringByReplacingOccurrencesOfString:@"-" withString:@""];
}
// MD5计算
+ (NSString *)md5:(NSString *)str {
const char *cStr = [str UTF8String];//转换成utf-8
unsigned char result[16];//开辟一个16字节(128位:md5加密出来就是128位/bit)的空间(一个字节=8字位=8个二进制数)
CC_MD5(cStr, strlen(cStr), result);
/*
extern unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md)官方封装好的加密方法
把cStr字符串转换成了32位的16进制数列(这个过程不可逆转) 存储到了result这个空间中
*/
return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
/*
x表示十六进制,%02X 意思是不足两位将用0补齐,如果多余两位则不影响
NSLog("%02X", 0x888); //888
NSLog("%02X", 0x4); //04
*/
}
//字典转json格式字符串:
+ (NSString *)dictionaryToJson:(NSDictionary *)dic {
NSError *parseError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&parseError];
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
实现算法第一步和第二步
// 加密
+ (NSString *)encode:(NSMutableDictionary *)jsonObject uuid:(NSString *)uuid {
jsonObject[NONCE_STR] = [self random]; //在原来的json里面添加随机字符串
// 对字典输出json string 精选排序
OrderedDictionary *orderedDictionary = [[OrderedDictionary alloc] initWithDictionary:jsonObject];
NSString *json = [orderedDictionary toJSONString];
NSString *jsonString = [json stringByAppendingString:uuid]; // 将json -> string
NSLog(@"jsonString %@", jsonString);
return [[self md5:jsonString] uppercaseString]; // -> md5 -> upper case
}
NSDictionary按属性排序导出json string
OrderedDictionary 代码地址
调用
// 对发送数据加密
+ (NSMutableDictionary *)encryptJSON:(NSMutableDictionary *)jsonObject uuid:(NSString *)uuid serviceId:(NSString *)serviceId {
[jsonObject setObject:[self encode:jsonObject uuid:uuid] forKey:SIGN];
[jsonObject setObject:serviceId forKey:SERVICE_ID];
return jsonObject;
}
校验
//校验收到的数据是否被篡改
+ (BOOL)checkEncrypt:(NSMutableDictionary *)jsonObject uuid:(NSString *)uuid {
NSString *sign = [jsonObject objectForKey:SIGN];
[jsonObject removeObjectForKey:SIGN];
return sign != nil && [sign isEqualToString:[self encode:jsonObject uuid:uuid]];
}
服务器
java 和Android一致, 只是不需要传serviceId
实现算法的第一步和第二步 (使用fastjson
, 可自动实现key值排序)
/**
* 计算jsonObject的json值, 并添加key
*
* @param jsonObject
* @param uuid, 和服务器约定的密钥, 可用password代替
* @return
*/
public static String encode(JSONObject jsonObject, String uuid) {
StringBuilder sb = new StringBuilder();
sb.append(jsonObject.toJSONString());
sb.append(uuid);
String md5 = MD5.stringMD5(sb.toString());
return md5;
}
调用
/**
* 对发送的json加密
*
* @param jsonObject
* @param uuid
* @param serviceId
* @return
*/
public static JSONObject encryptJSON(JSONObject jsonObject, String uuid) {
jsonObject.put("sign", encode(jsonObject, uuid));
return jsonObject;
}
校验服务器返回数据
/**
* 检查客户的提交json是否正确
*
* @param jsonObject
* @param uuid, 和客户端约定的密钥, 一般可用用户密码
* @return
*/
public static boolean checkEncrypt(JSONObject jsonObject, String uuid) {
String checkNumService = jsonObject.getString("sign");
jsonObject.remove("sign");
return checkNumService != null && checkNumService.equals(encode(jsonObject, uuid));
}
说明
由于iOS的Dictionary
的无序性, 这里提供一个自己实现的OrderedDictionary, 源码献上.