如何在iOS Objective-C中生成AWS签名?求提供相关示例代码
Hey there, I know it’s frustrating that AWS’s official Signature V4 examples don’t cover Objective-C for iOS—so I’ve put together a step-by-step guide with a complete working example to help you implement this.
First, let’s recap the core steps of Signature V4 (since you’ll need to understand what’s happening under the hood):
- Create a Canonical Request
- Create a String to Sign
- Generate a Signing Key
- Compute the final signature
All these steps rely on iOS’s built-in CommonCrypto framework, so make sure you link against it (it’s included by default in most iOS projects, but you’ll need to import the headers).
Prerequisites
Import these headers in your .m file:
#import <CommonCrypto/CommonHMAC.h> #import <CommonCrypto/CommonDigest.h> #import <Foundation/Foundation.h>
Complete Example Code
Here’s a reusable class method that handles all the Signature V4 logic. I’ve added detailed comments to walk you through each step:
@implementation AWSSignatureGenerator + (NSString *)generateAWSSignatureV4WithAccessKey:(NSString *)accessKey secretKey:(NSString *)secretKey region:(NSString *)region service:(NSString *)service httpMethod:(NSString *)httpMethod url:(NSURL *)url headers:(NSDictionary *)headers payload:(NSData *)payload { // Step 1: Get UTC timestamps required by AWS NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; dateFormatter.dateFormat = @"yyyyMMdd"; NSString *dateStamp = [dateFormatter stringFromDate:[NSDate date]]; dateFormatter.dateFormat = @"yyyyMMdd'T'HHmmss'Z'"; NSString *amzDate = [dateFormatter stringFromDate:[NSDate date]]; // Step 2: Build canonical URI (trim query string, handle empty path) NSString *canonicalURI = url.path; if (canonicalURI.length == 0) canonicalURI = @"/"; // Step 3: Build canonical query string (sorted, encoded key-value pairs) NSArray *queryItems = [[NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO] queryItems]; NSMutableArray *sortedQueryPairs = [NSMutableArray array]; for (NSURLQueryItem *item in [queryItems sortedArrayUsingComparator:^NSComparisonResult(NSURLQueryItem *a, NSURLQueryItem *b) { return [a.name compare:b.name options:NSCaseInsensitiveSearch]; }]) { NSString *encodedKey = [self urlEncodeString:item.name]; NSString *encodedValue = item.value ? [self urlEncodeString:item.value] : @""; [sortedQueryPairs addObject:[NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue]]; } NSString *canonicalQueryString = [sortedQueryPairs componentsJoinedByString:@"&"]; // Step 4: Build canonical headers (lowercase, sorted, key:value\n format) NSMutableDictionary *lowercaseHeaders = [NSMutableDictionary dictionary]; for (NSString *key in headers.allKeys) { lowercaseHeaders[[key lowercaseString]] = [[headers[key] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] lowercaseString]; } NSArray *sortedHeaderKeys = [lowercaseHeaders.allKeys sortedArrayUsingSelector:@selector(compare:)]; NSMutableString *canonicalHeaders = [NSMutableString string]; NSMutableString *signedHeaders = [NSMutableString string]; for (NSString *key in sortedHeaderKeys) { [canonicalHeaders appendFormat:@"%@:%@\n", key, lowercaseHeaders[key]]; [signedHeaders appendFormat:@"%@;", key]; } // Remove trailing semicolon from signed headers list signedHeaders = [NSMutableString stringWithString:[signedHeaders substringToIndex:signedHeaders.length - 1]]; // Step 5: Compute payload hash (SHA256 of request body, empty string hash if no payload) NSString *payloadHash = [self sha256HashForData:payload ?: [NSData data]]; // Step 6: Assemble full canonical request NSString *canonicalRequest = [NSString stringWithFormat:@"%@\n%@\n%@\n%@\n%@\n%@", httpMethod, canonicalURI, canonicalQueryString, canonicalHeaders, signedHeaders, payloadHash]; // Step 7: Build string to sign NSString *algorithm = @"AWS4-HMAC-SHA256"; NSString *credentialScope = [NSString stringWithFormat:@"%@/%@/%@/aws4_request", dateStamp, region, service]; NSString *stringToSign = [NSString stringWithFormat:@"%@\n%@\n%@\n%@", algorithm, amzDate, credentialScope, [self sha256HashForString:canonicalRequest]]; // Step 8: Generate signing key using iterative HMAC-SHA256 NSData *signingKey = [self generateSigningKeyWithSecretKey:secretKey dateStamp:dateStamp region:region service:service]; // Step 9: Compute final signature NSString *signature = [self hmacSHA256ForString:stringToSign withKey:signingKey]; // Return full authorization header (you can also return just the signature if needed) NSString *authorizationHeader = [NSString stringWithFormat:@"%@ Credential=%@/%@, SignedHeaders=%@, Signature=%@", algorithm, accessKey, credentialScope, signedHeaders, signature]; return authorizationHeader; } // Helper: URL encode string per AWS specs (only unreserved chars remain unencoded) + (NSString *)urlEncodeString:(NSString *)string { NSCharacterSet *allowedChars = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"]; return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedChars]; } // Helper: SHA256 hash for raw data + (NSString *)sha256HashForData:(NSData *)data { unsigned char digest[CC_SHA256_DIGEST_LENGTH]; CC_SHA256(data.bytes, (CC_LONG)data.length, digest); NSMutableString *hash = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { [hash appendFormat:@"%02x", digest[i]]; } return hash; } // Helper: SHA256 hash for string + (NSString *)sha256HashForString:(NSString *)string { return [self sha256HashForData:[string dataUsingEncoding:NSUTF8StringEncoding]]; } // Helper: Generate AWS4 signing key (iterative HMAC steps) + (NSData *)generateSigningKeyWithSecretKey:(NSString *)secretKey dateStamp:(NSString *)dateStamp region:(NSString *)region service:(NSString *)service { NSString *kSecret = [NSString stringWithFormat:@"AWS4%@", secretKey]; NSData *kDate = [self hmacSHA256DataForString:dateStamp withKey:[kSecret dataUsingEncoding:NSUTF8StringEncoding]]; NSData *kRegion = [self hmacSHA256DataForString:region withKey:kDate]; NSData *kService = [self hmacSHA256DataForString:service withKey:kRegion]; NSData *kSigning = [self hmacSHA256DataForString:@"aws4_request" withKey:kService]; return kSigning; } // Helper: HMAC-SHA256 for string, returns hex string + (NSString *)hmacSHA256ForString:(NSString *)string withKey:(NSData *)key { NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; unsigned char digest[CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, key.bytes, key.length, data.bytes, data.length, digest); NSMutableString *hash = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { [hash appendFormat:@"%02x", digest[i]]; } return hash; } // Helper: HMAC-SHA256 for string, returns raw data + (NSData *)hmacSHA256DataForString:(NSString *)string withKey:(NSData *)key { NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; unsigned char digest[CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, key.bytes, key.length, data.bytes, data.length, digest); return [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; } @end
How to Use This
Here’s a quick example of calling this method for a GET request to an S3 bucket:
// 1. Define your request details NSURL *s3Url = [NSURL URLWithString:@"https://my-bucket.s3.us-east-1.amazonaws.com/my-file.txt"]; NSDictionary *requestHeaders = @{@"Host": @"my-bucket.s3.us-east-1.amazonaws.com"}; // 2. Generate signature/authorization header NSString *authorization = [AWSSignatureGenerator generateAWSSignatureV4WithAccessKey:@"YOUR_AWS_ACCESS_KEY" secretKey:@"YOUR_AWS_SECRET_KEY" region:@"us-east-1" service:@"s3" httpMethod:@"GET" url:s3Url headers:requestHeaders payload:nil]; // 3. Add headers to your request NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:s3Url]; [request setValue:authorization forHTTPHeaderField:@"Authorization"]; // Don't forget to add the X-Amz-Date header (you can extract it from the generator method if needed) [request setValue:[AWSSignatureGenerator getCurrentAmzDate] forHTTPHeaderField:@"X-Amz-Date"];
Key Notes
- Date Handling: Always use UTC timestamps—AWS will reject signatures generated with local time.
- Canonical Headers: Every header included in your request must be added to the
headersdictionary, and they must be lowercased and sorted correctly. - Payload Hash: For POST/PUT requests with a body, pass the raw payload data. For empty bodies, the method automatically uses the SHA256 hash of an empty string.
- URL Encoding: Follow AWS’s strict encoding rules—only unreserved characters (A-Z, a-z, 0-9, -, _, ., ~) are left unencoded.
内容的提问来源于stack exchange,提问作者shivang pandya

