// // KBSignUtils.m // keyBoard // // Created by Mac on 2025/12/4. // #import "KBSignUtils.h" #import @implementation KBSignUtils static NSString *const KBSignAppId = @"loveKeyboard"; static NSString *const KBSignSecret = @"kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H"; static NSString *KBSignStringFromObject(id obj) { if (!obj || obj == (id)kCFNull) { return nil; } if ([obj isKindOfClass:[NSString class]]) { return (NSString *)obj; } if ([obj isKindOfClass:[NSNumber class]]) { return [(NSNumber *)obj stringValue]; } if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) { NSJSONWritingOptions options = 0; if (@available(iOS 11.0, *)) { options = NSJSONWritingSortedKeys; } NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:options error:nil]; if (data.length > 0) { return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } } return [obj description]; } + (NSString *)urlEncode:(NSString *)value { if (!value) return @""; // 按 application/x-www-form-urlencoded 规则编码(更贴近后端常见实现) NSMutableCharacterSet *allowed = [NSMutableCharacterSet alphanumericCharacterSet]; [allowed addCharactersInString:@"-._*"]; NSString *encoded = [value stringByAddingPercentEncodingWithAllowedCharacters:allowed]; if (!encoded) { return value; } // 空格改为 +(URLEncoder 风格) encoded = [encoded stringByReplacingOccurrencesOfString:@"%20" withString:@"+"]; return encoded; } + (NSString *)hmacSHA256:(NSString *)data secret:(NSString *)secret { const char *cKey = [secret cStringUsingEncoding:NSUTF8StringEncoding]; const char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding]; unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC); // 转十六进制小写字符串 NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { [output appendFormat:@"%02x", cHMAC[i]]; } return output; } + (NSString *)signWithParams:(NSDictionary *)params secret:(NSString *)secret { NSString *dataString = [self signSourceStringWithParams:params secret:secret]; return [self hmacSHA256:dataString secret:secret]; } + (NSString *)signSourceStringWithParams:(NSDictionary *)params secret:(NSString *)secret { // 1. 过滤空值 & sign 自身 NSMutableDictionary *filtered = [NSMutableDictionary dictionary]; [params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { if (!obj || obj.length == 0) return; if ([[key lowercaseString] isEqualToString:@"sign"]) return; filtered[key] = obj; }]; // 2. 按 key 字典序排序 NSArray *sortedKeys = [[filtered allKeys] sortedArrayUsingSelector:@selector(compare:)]; // 3. 拼接 key=value&key2=value2...&secret=xxx NSMutableArray *components = [NSMutableArray array]; for (NSString *key in sortedKeys) { NSString *value = filtered[key] ?: @""; NSString *encodedValue = [self urlEncode:value]; NSString *part = [NSString stringWithFormat:@"%@=%@", key, encodedValue]; [components addObject:part]; } NSString *encodedSecret = [self urlEncode:secret ?: @""]; NSString *secretPart = [NSString stringWithFormat:@"secret=%@", encodedSecret]; [components addObject:secretPart]; return [components componentsJoinedByString:@"&"]; } + (NSString *)currentTimestamp { NSTimeInterval ts = [[NSDate date] timeIntervalSince1970]; long long seconds = (long long)ts; return [NSString stringWithFormat:@"%lld", seconds]; } + (NSString *)generateNonceWithLength:(NSUInteger)length { // 用 UUID 生成,去掉 - , 再截取前 length 位 NSString *uuid = [[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""]; if (length == 0 || length > uuid.length) { return uuid; } return [uuid substringToIndex:length]; } + (NSDictionary *)signHeadersWithBodyParams:(NSDictionary *)bodyParams { NSString *timestamp = [self currentTimestamp]; NSString *nonce = [self generateNonceWithLength:16]; NSMutableDictionary *signParams = [NSMutableDictionary dictionary]; if (KBSignAppId.length > 0) { signParams[@"appId"] = KBSignAppId; } if (timestamp.length > 0) { signParams[@"timestamp"] = timestamp; } if (nonce.length > 0) { signParams[@"nonce"] = nonce; } if ([bodyParams isKindOfClass:[NSDictionary class]] && bodyParams.count > 0) { [bodyParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if (![key isKindOfClass:[NSString class]]) { return; } NSString *value = KBSignStringFromObject(obj); if (value.length == 0) { return; } signParams[(NSString *)key] = value; }]; } NSString *sign = [self signWithParams:signParams secret:KBSignSecret ?: @""]; NSMutableDictionary *headers = [NSMutableDictionary dictionary]; if (sign.length > 0) { headers[@"X-Sign"] = sign; } if (KBSignAppId.length > 0) { headers[@"X-App-Id"] = KBSignAppId; } if (timestamp.length > 0) { headers[@"X-Timestamp"] = timestamp; } if (nonce.length > 0) { headers[@"X-Nonce"] = nonce; } return [headers copy]; } @end