Objective c iOS中的亚马逊产品广告API签名

Objective c iOS中的亚马逊产品广告API签名,objective-c,ios,amazon-web-services,amazon,amazon-product-api,Objective C,Ios,Amazon Web Services,Amazon,Amazon Product Api,我正在尝试在iOS应用程序中访问亚马逊的产品广告API。创建签名似乎是困难的部分。在本页上: 它说“使用SHA256哈希算法计算符合RFC2104的HMAC”。Amazon还为您提供了一个java类: 有人知道我如何在Objective-C中实现这一点吗?我查看了AWS iOS SDK,但它似乎没有包括产品广告API。实际上,AWS iOS SDK有一个静态方法来处理所有身份验证情况。 也许你应该浏览一下AmazonAuthUtils.h: +(NSString *)HMACSign:(NS

我正在尝试在iOS应用程序中访问亚马逊的产品广告API。创建签名似乎是困难的部分。在本页上:

它说“使用SHA256哈希算法计算符合RFC2104的HMAC”。Amazon还为您提供了一个java类:


有人知道我如何在Objective-C中实现这一点吗?我查看了AWS iOS SDK,但它似乎没有包括产品广告API。

实际上,AWS iOS SDK有一个静态方法来处理所有身份验证情况。 也许你应该浏览一下AmazonAuthUtils.h:

+(NSString *)HMACSign:(NSData *)data withKey:(NSString *)key usingAlgorithm:(CCHmacAlgorithm)algorithm;
+(NSData *)sha256HMac:(NSData *)data withKey:(NSString *)key;

您可以在文档中找到它:

只是为了给camelcc的出色观察添加一点内容。这确实适用于对Amazon产品广告API的请求进行签名。为了让它工作起来,我不得不胡闹了一下

安装SDK并导入

注意字符串中需要新行字符,我的无符号字符串如下所示

@“GET\necs.amazonaws.com\n/onca/xml\nAWSAccessKeyId=&AssociateTag=&Keywords=harry%20potter&Operation=ItemSearch&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2012-07-03T10%3A52%3A21.000Z&Version=2011-08-01”

任何地方都没有空格,但
\n
字符位于正确的位置。将其转换为
NSData
,如下所示

NSData*dataToSign=[unsignedString dataUsingEncoding:NSUTF8StringEncoding]

然后打电话

[AmazonAuthUtils HMACSign:dataToSign with KEY:SECRET\u KEY usingAlgorithm:kchmacalgsha256]

这将以
NSString
的形式返回您的签名。您需要对此进行URL编码(即为%0x符号交换非法/不安全的字符(即“=”转换为“%3D”))


一旦这样做了,坚持在你的要求,希望你是好去

p-double post缺少几个步骤

在构造无符号字符串之前,您将需要获得适当的时间戳值

NSTimeZone *zone = [NSTimeZone defaultTimeZone];                //get the current application default time zone
NSInteger interval = [zone secondsFromGMTForDate:[NSDate date]];//sec Returns the time difference of the current application with the world standard time (Green Venice time)

NSDate *nowDate = [NSDate dateWithTimeIntervalSinceNow:interval];
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];

[formatter setTimeZone:[NSTimeZone systemTimeZone]];// get current date/time
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone systemTimeZone]];

// display in 12HR/24HR (i.e. 11:25PM or 23:25) format according to User Settings
[dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
NSString *currentTime = [dateFormatter stringFromDate:nowDate];

NSString* encodedTime = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) currentTime,NULL, CFSTR("!*'();:@&=+$,/?%#[]"),kCFStringEncodingUTF8));

NSString* unsignedString = [NSString stringWithFormat:@"GET\nwebservices.amazon.com\n/onca/xml\nAWSAccessKeyId=AKIAI443QEMWI6KW55QQ&AssociateTag=sajjmanz-20&Condition=All&IdType=ASIN&ItemId=3492264077&Operation=ItemLookup&ResponseGroup=Images%%2CItemAttributes%%2COffers&Service=AWSECommerceService&Timestamp=%@&Version=2011-08-01", encodedTime];
一旦对日期进行了url友好编码,剩下的步骤就像一个符咒


最后,我还使用上面列出的CFURLCreateStringByAddingPercentEscapes对AmazonAuthUtils HMACSign消息调用生成的字符串进行编码

未经亚马逊明确书面同意,在iOS应用程序中使用亚马逊的产品广告API是不“合法”的(根据亚马逊自己的指导原则),我错了吗?

请查看我的亚马逊产品广告客户端

一些带有requesst序列化的代码:

NSString * const RWMAmazonProductAdvertisingStandardRegion = @"webservices.amazon.com";
NSString * const RWMAmazonProductAdvertisingAWSAccessKey = @"AWSAccessKeyId";
NSString * const RWMAmazonProductAdvertisingTimestampKey = @"Timestamp";
NSString * const RWMAmazonProductAdvertisingSignatureKey = @"Signature";
NSString * const RWMAmazonProductAdvertisingVersionKey = @"Version";
NSString * const RWMAmazonProductAdvertisingCurrentVersion = @"2011-08-01";

NSData * RWMHMACSHA256EncodedDataFromStringWithKey(NSString *string, NSString *key) {
    NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding];
    CCHmacContext context;
    const char *keyCString = [key cStringUsingEncoding:NSASCIIStringEncoding];

    CCHmacInit(&context, kCCHmacAlgSHA256, keyCString, strlen(keyCString));
    CCHmacUpdate(&context, [data bytes], [data length]);

    unsigned char digestRaw[CC_SHA256_DIGEST_LENGTH];
    NSUInteger digestLength = CC_SHA256_DIGEST_LENGTH;

    CCHmacFinal(&context, digestRaw);

    return [NSData dataWithBytes:digestRaw length:digestLength];
}

NSString * RWMISO8601FormatStringFromDate(NSDate *date) {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
    [dateFormatter setDateFormat:@"YYYY-MM-dd'T'HH:mm:ss'Z'"];
    [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];

    return [dateFormatter stringFromDate:date];
}

NSString * RWMBase64EncodedStringFromData(NSData *data) {

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
    return [data base64EncodedStringWithOptions:0];
#else
    return [data base64Encoding];
#endif

}

//http://docs.aws.amazon.com/AWSECommerceService/latest/DG/rest-signature.html

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError * __autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    if (self.accessKey && self.secret) {
        NSMutableDictionary *mutableParameters = [parameters mutableCopy];
        NSString *timestamp = RWMISO8601FormatStringFromDate([NSDate date]);

        if (!mutableParameters[RWMAmazonProductAdvertisingAWSAccessKey]) {
            [mutableParameters setObject:self.accessKey forKey:RWMAmazonProductAdvertisingAWSAccessKey];
        }
        mutableParameters[RWMAmazonProductAdvertisingVersionKey] = RWMAmazonProductAdvertisingCurrentVersion;
        mutableParameters[RWMAmazonProductAdvertisingTimestampKey] = timestamp;

        NSMutableArray *canonicalStringArray = [[NSMutableArray alloc] init];
        for (NSString *key in [[mutableParameters allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
            id value = [mutableParameters objectForKey:key];
            [canonicalStringArray addObject:[NSString stringWithFormat:@"%@=%@", key, value]];
        }
        NSString *canonicalString = [canonicalStringArray componentsJoinedByString:@"&"];
        canonicalString = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                                    (__bridge CFStringRef)canonicalString,
                                                                                    NULL,
                                                                                    CFSTR(":,"),
                                                                                    kCFStringEncodingUTF8));

        NSString *method = [request HTTPMethod];

        NSString *signature = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",method,self.region,self.formatPath,canonicalString];

        NSData *encodedSignatureData = RWMHMACSHA256EncodedDataFromStringWithKey(signature,self.secret);
        NSString *encodedSignatureString = RWMBase64EncodedStringFromData(encodedSignatureData);

        encodedSignatureString = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                                           (__bridge CFStringRef)encodedSignatureString,
                                                                                           NULL,
                                                                                           CFSTR("+="),
                                                                                           kCFStringEncodingUTF8));

        canonicalString = [canonicalString stringByAppendingFormat:@"&%@=%@",RWMAmazonProductAdvertisingSignatureKey,encodedSignatureString];

        mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", canonicalString]];

    } else {
        if (error) {
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Access Key and Secret Required", @"RWMAmazonProductAdvertisingManager", nil)};
            *error = [[NSError alloc] initWithDomain:RWMAmazonProductAdvertisingManagerErrorDomain code:NSURLErrorUserAuthenticationRequired userInfo:userInfo];
        }
    }

    return mutableRequest;

}

与现代OS X中不推荐使用的CommonCrypto不同,您还可以使用扇区转换:

CFErrorRef error = NULL;

SecTransformRef digestRef = SecDigestTransformCreate(kSecDigestHMACSHA2, 256, &error);
SecTransformSetAttribute(digestRef, kSecTransformInputAttributeName, (__bridge CFDataRef)self, &error);
SecTransformSetAttribute(digestRef, kSecDigestHMACKeyAttribute, (__bridge CFDataRef)key, &error);

CFDataRef resultData = SecTransformExecute(digestRef, &error);
NSData* hashData = (__bridge NSData*)resultData;

CFRelease(digestRef);

Swift 2.0

下面是一个函数,它将为Swift的一组参数签名。请注意,此代码要求安装
Alamofire
AWSCore
CoCoCoaPods。您还需要向Objective-C桥接头添加
#import
,否则将找不到
kCCHmacAlgSHA256

private func signedParametersForParameters(parameters: [String: String]) -> [String: String] {
    let sortedKeys = Array(parameters.keys).sort(<)

    var components: [(String, String)] = []
    for key in sortedKeys {
        components += ParameterEncoding.URLEncodedInURL.queryComponents(key, parameters[key]!)
    }

    let query = (components.map { "\($0)=\($1)" } as [String]).joinWithSeparator("&")

    let stringToSign = "GET\nwebservices.amazon.com\n/onca/xml\n\(query)"
    let dataToSign = stringToSign.dataUsingEncoding(NSUTF8StringEncoding)
    let signature = AWSSignatureSignerUtility.HMACSign(dataToSign, withKey: kAmazonAccessSecretKey, usingAlgorithm: UInt32(kCCHmacAlgSHA256))!

    let signedParams = parameters + ["Signature": signature]

    return signedParams
}
最后,timestampFormatter声明如下:

let operationParams: [String: String] = ["Service": "AWSECommerceService", "Operation": "ItemLookup", "ItemId": "045242127733", "IdType": "UPC", "ResponseGroup": "Images,ItemAttributes", "SearchIndex": "All"]
let keyParams = ["AWSAccessKeyId": kAmazonAccessID, "AssociateTag": kAmazonAssociateTag, "Timestamp": timestampFormatter.stringFromDate(NSDate())]
let fullParams = operationParams + keyParams

let signedParams = signedParametersForParameters(fullParams)

Alamofire.request(.GET, "http://webservices.amazon.com/onca/xml", parameters: signedParams).responseString { (response) in
    print("Success: \(response.result.isSuccess)")
    print("Response String: \(response.result.value)")
}
private let timestampFormatter: NSDateFormatter

init() {
    timestampFormatter = NSDateFormatter()
    timestampFormatter.dateFormat = AWSDateISO8601DateFormat3
    timestampFormatter.timeZone = NSTimeZone(name: "GMT")
    timestampFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
}

您可以使用/修改以满足您的需要,但所有必要的内容都应该在那里。

感谢您在此页面上提供的所有答案。以下是对我有效的方法(Swift 3.0):

播客文件:

pod 'AWSAPIGateway', '~> 2.4.7'
Swift代码

static let kAmazonAccessID = "BLAH BLAH BLAH"
static let kAmazonAccessSecretKey = "BLAH BLAH BLAH"

static let kAmazonAssociateTag = "BLAH BLAH BLAH"
private let timestampFormatter: DateFormatter

init() {
    timestampFormatter = DateFormatter()
    timestampFormatter.timeZone = TimeZone(identifier: "GMT")
    timestampFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss'Z'"
    timestampFormatter.locale = Locale(identifier: "en_US_POSIX")
}

private func signedParametersForParameters(parameters: [String: String]) -> [String: String] {
    let sortedKeys = Array(parameters.keys).sorted(by: <)

    let query = sortedKeys.map { String(format: "%@=%@", $0, parameters[$0] ?? "") }.joined(separator: "&")

    let stringToSign = "GET\nwebservices.amazon.com\n/onca/xml\n\(query)"

    let dataToSign = stringToSign.data(using: String.Encoding.utf8)
    let signature = AWSSignatureSignerUtility.hmacSign(dataToSign, withKey: AmazonAPI.kAmazonAccessSecretKey, usingAlgorithm: UInt32(kCCHmacAlgSHA256))!

    var signedParams = parameters;
    signedParams["Signature"] = urlEncode(signature)

    return signedParams
}

public func urlEncode(_ input: String) -> String {
    let allowedCharacterSet = (CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[] ").inverted)

    if let escapedString = input.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) {
        return escapedString
    }

    return ""
}

func send(url: String) -> String {
    guard let url = URL(string: url) else {
        print("Error! Invalid URL!") //Do something else
        return ""
    }

    let request = URLRequest(url: url)
    let semaphore = DispatchSemaphore(value: 0)

    var data: Data? = nil

    URLSession.shared.dataTask(with: request) { (responseData, _, _) -> Void in
        data = responseData
        semaphore.signal()
    }.resume()

    semaphore.wait(timeout: .distantFuture)

    let reply = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
    return reply
}

嘿,我在生成签名时遇到了问题。我遵循你提到的同样方法。你能帮我解决这个问题吗。获取ecs.amazonaws.com/onca/xml AWSAccessKeyId=&AssociateTag=&Keywords=harry potter&Operation=ItemSearch&SearchIndex=Books&Service=awsecomerceseservice&Timestamp=2012-07-27T11%3A12%3A46Z&Version=2011-08-01您是否将“/n”换行符放在了正确的位置,与我放在了相同的位置?我解决了这个问题,您的回答对我帮助很大。。。谢谢+1用于此,仅用于此附加项。要正确获取其中的时间戳:NSDate*currentTime=[NSDate-date];NSString*时间戳=[currentTime StringWithISO8601格式];NSString*timestampAmazon=[TimestampStringWithUrlEncoding];在今天的AWSCore和相关SDK中,什么是等效的?我没有看到任何函数或框架提供相同的功能。也许他在加利福尼亚州,那里clickwrap协议的某些条款已经失效。我希望能够使用这个api。你认为它会对iOS开放吗?我不太了解。有一件事我可以肯定地告诉你——许多开发者永远不会想到去阅读这些指南并在他们的应用程序中使用它们,除非有其他技术障碍或应用商店拒绝它们。我只是认为亚马逊会从允许在iOS应用程序中使用API以及允许附属程序中受益匪浅(这当然也会让开发者受益。)我希望“禁令”能很快解除。我相信这个答案会更好,如果它包含了它工作原理的要点。毕竟,只有链接的答案很容易导致链接腐烂。但我不相信这在iOS上行得通,因为iOS不存在SecDigestTransformCreate()(从iOS 8.0开始)。这似乎是我所看到的最好的答案,但它仍然给了我一个错误:…signaturedesnotmatch我们计算的请求签名与您提供的签名不匹配。请检查您的AWS秘密访问密钥和签名方法…我已经尝试解决这个问题好几天了,非常感谢您的帮助。:)非常感谢。您是否有机会获得Objective-C中的此代码?看起来很好。恐怕我没有ObjC用的,主要是因为它用的是Alamofire的东西,我认为这只是Swift。也许一些顶级答案对ObjC有帮助?我上次看这个已经有一段时间了。@CoryImdieke在“ResponseGroup”中:“Images,itemtattributes”如果它的两个值类似于Images,attributes,那么它表示我们计算的请求签名与您提供的签名不匹配。检查您的AWS秘密访问密钥和签名方法,或者如果其responsegroup值类似于“images”,则我可以abl
public func getProductPrice(_ asin: AmazonStandardIdNumber) -> Double {

    let operationParams: [String: String] = [
        "Service": "AWSECommerceService",
        "Operation": "ItemLookup",
        "ResponseGroup": "Offers",
        "IdType": "ASIN",
        "ItemId": asin,
        "AWSAccessKeyId": urlEncode(AmazonAPI.kAmazonAccessID),
        "AssociateTag": urlEncode(AmazonAPI.kAmazonAssociateTag),
        "Timestamp": urlEncode(timestampFormatter.string(from: Date())),]

    let signedParams = signedParametersForParameters(parameters: operationParams)

    let query = signedParams.map { "\($0)=\($1)" }.joined(separator: "&")
    let url = "http://webservices.amazon.com/onca/xml?" + query

    let reply = send(url: url)

    // USE THE RESPONSE
}