Or How to Derive a Version 4 Signing Key for a Mac App
I have been working on a major update to Captured, and heard back from one of the beta testers that uses S3 in the eu-central-1
region. Turns out that is one of the newer regions that requires the AWS4-HMAC-SHA256
Authorization Method. After a bit of research I found Amazon’s Signature Version 4 Signing Process which describes how to do the signing. They even provide a few code examples, however they didn’t have any that used Swift or Objective-C.
I really wanted to use swift, since the vast majority of Captured is written in swift, but I quicly discovered the pain that is C-interop. So I wrote an Objective-C class that will deal with that. And after a simple line in my bridging header it was very easy to use in swift.
If you want to do this in Objective-C, here’s the tl;dr:
+ (NSData *)hmacSha256ForString:(NSString *)stringToSign withKey:(NSData *)key {
NSData *dataToSign = [stringToSign dataUsingEncoding:NSUTF8StringEncoding];
unsigned char rawDigest[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, [key bytes], [key length], [dataToSign bytes], [dataToSign length], rawDigest);
return [[NSData alloc] initWithBytes:rawDigest length:CC_SHA256_DIGEST_LENGTH];
}
+ (NSData *)getSignatureKey:(NSString *)key dateStamp:(NSString *)dateStamp regionName:(NSString *)regionName serviceName:(NSString *)serviceName {
NSData *kSecret = [[NSString stringWithFormat:@"AWS4%@", key] dataUsingEncoding:NSUTF8StringEncoding];
NSData *kDate = [self hmacSha256ForString: dateStamp withKey:kSecret];
NSData *kRegion = [self hmacSha256ForString: regionName withKey:kDate];
NSData *kService = [self hmacSha256ForString: serviceName withKey:kRegion];
NSData *kSigning = [self hmacSha256ForString: @"aws4_request" withKey:kService];
return kSigning;
}
I added a few NSLog
statements to test thigns along the way. This turned out to be very importatn, because I had a few bugs (mostly around buffer lengths) that would make things look like they were working. The first few steps matched the reference output, but not all of them. I needed all of the steps to work. Here is my test code that I ran to get things working:
#import <CommonCrypto/CommonHMAC.h>
#import "AWS4SigningKey.h"
@implementation AWS4SigningKey
+ (NSData *)hmacSha256ForString:(NSString *)stringToSign withKey:(NSData *)key {
NSData *dataToSign = [stringToSign dataUsingEncoding:NSUTF8StringEncoding];
unsigned char rawDigest[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, [key bytes], [key length], [dataToSign bytes], [dataToSign length], rawDigest);
return [[NSData alloc] initWithBytes:rawDigest length:CC_SHA256_DIGEST_LENGTH];
}
+ (NSData *)getSignatureKey:(NSString *)key
dateStamp:(NSString *)dateStamp
regionName:(NSString *)regionName
serviceName:(NSString *)serviceName {
NSData *kSecret = [[NSString stringWithFormat:@"AWS4%@", key] dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"%@", [kSecret description]);
NSData *kDate = [self hmacSha256ForString: dateStamp withKey:kSecret];
NSLog(@"%@", [kDate description]);
NSData *kRegion = [self hmacSha256ForString: regionName withKey:kDate];
NSLog(@"%@", [kRegion description]);
NSData *kService = [self hmacSha256ForString: serviceName withKey:kRegion];
NSLog(@"%@", [kService description]);
NSData *kSigning = [self hmacSha256ForString: @"aws4_request" withKey:kService];
NSLog(@"%@", [kSigning description]);
return kSigning;
}
@end
Then when I run this bit of Swift code:
let key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
let dateStamp = "20120215"
let regionName = "us-east-1"
let serviceName = "iam"
AWS4SigningKey.getSignatureKey(key, dateStamp: dateStamp, regionName: regionName, serviceName: serviceName)
This output:
2016-02-19 23:00:34.493 Captured[29081:3568690] <41575334 774a616c 72585574 6e46454d 492f4b37 4d44454e 472b6250 78526669 43594558 414d504c 454b4559>
2016-02-19 23:00:34.493 Captured[29081:3568690] <969fbb94 feb542b7 1ede6f87 fe4d5fa2 9c789342 b0f40747 4670f0c2 489e0a0d>
2016-02-19 23:00:34.493 Captured[29081:3568690] <69daa020 9cd9c5ff 5c8ced46 4a696fd4 252e9814 30b10e3d 3fd8e2f1 97d7a70c>
2016-02-19 23:00:34.493 Captured[29081:3568690] <f72cfd46 f26bc464 3f06a11e abb6c0ba 18780c19 a8da0c31 ace67126 5e3c87fa>
2016-02-19 23:00:34.493 Captured[29081:3568690] <f4780e2d 9f65fa89 5f9c67b3 2ce1baf0 b0d8a435 05a000a1 a9e090d4 14db404d>
Which if you delete a few extra spaces, you’ll notice matches the reference from the AWS documentation:
kSecret = '41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559'
kDate = '969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d'
kRegion = '69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c'
kService = 'f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa'
kSigning = 'f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d'
High-fives all around!
I’d prefer a pure-Swift solution, but this bit of objective-c and the fact that I can omit all the hacky-marshaling to C functions makes me pretty happy. You can find the full class definition here.