2025-12-04 20:27:26 +08:00
|
|
|
|
//
|
|
|
|
|
|
// KBSignUtils.m
|
|
|
|
|
|
// keyBoard
|
|
|
|
|
|
//
|
|
|
|
|
|
// Created by Mac on 2025/12/4.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#import "KBSignUtils.h"
|
|
|
|
|
|
#import <CommonCrypto/CommonCrypto.h>
|
|
|
|
|
|
|
|
|
|
|
|
@implementation KBSignUtils
|
|
|
|
|
|
|
2026-02-24 14:59:06 +08:00
|
|
|
|
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];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 20:27:26 +08:00
|
|
|
|
+ (NSString *)urlEncode:(NSString *)value {
|
|
|
|
|
|
if (!value) return @"";
|
2026-02-03 20:22:28 +08:00
|
|
|
|
// 按 application/x-www-form-urlencoded 规则编码(更贴近后端常见实现)
|
|
|
|
|
|
NSMutableCharacterSet *allowed = [NSMutableCharacterSet alphanumericCharacterSet];
|
|
|
|
|
|
[allowed addCharactersInString:@"-._*"];
|
2025-12-04 20:27:26 +08:00
|
|
|
|
NSString *encoded = [value stringByAddingPercentEncodingWithAllowedCharacters:allowed];
|
2026-02-03 20:22:28 +08:00
|
|
|
|
if (!encoded) {
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 空格改为 +(URLEncoder 风格)
|
|
|
|
|
|
encoded = [encoded stringByReplacingOccurrencesOfString:@"%20" withString:@"+"];
|
|
|
|
|
|
return encoded;
|
2025-12-04 20:27:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (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<NSString *, NSString *> *)params
|
|
|
|
|
|
secret:(NSString *)secret {
|
2026-02-03 20:22:28 +08:00
|
|
|
|
NSString *dataString = [self signSourceStringWithParams:params secret:secret];
|
|
|
|
|
|
return [self hmacSHA256:dataString secret:secret];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (NSString *)signSourceStringWithParams:(NSDictionary<NSString *, NSString *> *)params
|
|
|
|
|
|
secret:(NSString *)secret {
|
2025-12-04 20:27:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 1. 过滤空值 & sign 自身
|
|
|
|
|
|
NSMutableDictionary<NSString *, NSString *> *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<NSString *> *sortedKeys = [[filtered allKeys] sortedArrayUsingSelector:@selector(compare:)];
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 拼接 key=value&key2=value2...&secret=xxx
|
|
|
|
|
|
NSMutableArray<NSString *> *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];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 20:22:28 +08:00
|
|
|
|
NSString *encodedSecret = [self urlEncode:secret ?: @""];
|
2025-12-04 20:27:26 +08:00
|
|
|
|
NSString *secretPart = [NSString stringWithFormat:@"secret=%@", encodedSecret];
|
|
|
|
|
|
[components addObject:secretPart];
|
|
|
|
|
|
|
2026-02-03 20:22:28 +08:00
|
|
|
|
return [components componentsJoinedByString:@"&"];
|
2025-12-04 20:27:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (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];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 14:59:06 +08:00
|
|
|
|
+ (NSDictionary<NSString *, NSString *> *)signHeadersWithBodyParams:(NSDictionary *)bodyParams {
|
|
|
|
|
|
NSString *timestamp = [self currentTimestamp];
|
|
|
|
|
|
NSString *nonce = [self generateNonceWithLength:16];
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableDictionary<NSString *, NSString *> *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<NSString *, NSString *> *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];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 20:27:26 +08:00
|
|
|
|
@end
|