swiftamazon-web-servicesaws-api-gatewayhmacapple-cryptokit

Derive An AWS Signing Key For Signature Version 4


I am attempting to generate a signing key for AWS according to AWS's documentation here for an iOS application. The documentation is pretty good however, it doesn't provide an example using Swift. Apple provides CryptoKit which should be the right framework, but I haven't been able to puzzle it out.

Ruby Example

def getSignatureKey key, dateStamp, regionName, serviceName
    kDate = OpenSSL::HMAC.digest('sha256', "AWS4" + key, dateStamp)
    kRegion = OpenSSL::HMAC.digest('sha256', kDate, regionName)
    kService = OpenSSL::HMAC.digest('sha256', kRegion, serviceName)
    kSigning = OpenSSL::HMAC.digest('sha256', kService, "aws4_request")

    kSigning
end

Sample Inputs from AWS Docs

key = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20120215'
regionName = 'us-east-1'
serviceName = 'iam'

Should output

kSecret  = '41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559'
kDate    = '969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d'
kRegion  = '69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c'
kService = 'f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa'
kSigning = 'f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d'

My attempt (note you have to append AWS to the key according to the docs)

import Foundation
import CryptoKit

let key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
let dateStamp = "20120215"
let regionName = "us-east-1"
let serviceName = "iam"

let keyData = Data("AWS\(key)".utf8)
let symmetricKey = SymmetricKey(data: keyData)

let dateStampData = Data(dateStamp.utf8)
let signature = HMAC<SHA256>.authenticationCode(for: dateStampData, using: symmetricKey)

let skeyString = keyData.map { String(format: "%02hhx", $0) }.joined()
print("kSecret \t= \(skeyString)")

let kDateString = Data(signature).map { String(format: "%02hhx", $0) }.joined()
print("kDate \t\t= \(kDateString)")

The first one is correct, so it seems I have the initial key correct, but when trying to apply it to dateStamp it doesn't match.

Outputs

kSecret     = 415753774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559
kDate       = 2226579f8b317a03ec325a8c8b3d27cf465ce52787455e1880039824b4ba0e25

Solution

  • Of course the moment I post the question I find the issue. The original issue was I was appending AWS instead of AWS4 the string appeared to be correct for kSecret because I was looking at the first set and last set of digits. Here is the solution for anyone looking to do the same.

    import Foundation
    import CryptoKit
    
    let key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
    let dateStamp = "20120215"
    let regionName = "us-east-1"
    let serviceName = "iam"
    
    let keyData = Data("AWS4\(key)".utf8)
    let dateStampData = Data(dateStamp.utf8)
    let regionNameData = Data(regionName.utf8)
    let serviceNameData = Data(serviceName.utf8)
    let signingData = Data("aws4_request".utf8)
    
    var symmetricKey = SymmetricKey(data: keyData)
    let dateSHA256 = HMAC<SHA256>.authenticationCode(for: dateStampData, using: symmetricKey)
    
    symmetricKey = SymmetricKey(data: Data(dateSHA256))
    let regionSHA256 = HMAC<SHA256>.authenticationCode(for: regionNameData, using: symmetricKey)
    
    symmetricKey = SymmetricKey(data: Data(regionSHA256))
    let serviceNameSHA256 = HMAC<SHA256>.authenticationCode(for: serviceNameData, using: symmetricKey)
    
    symmetricKey = SymmetricKey(data: Data(serviceNameSHA256))
    let signingSHA256 = HMAC<SHA256>.authenticationCode(for: signingData, using: symmetricKey)
    
    let skeyString = keyData.map { String(format: "%02hhx", $0) }.joined()
    print("kSecret \t= \(skeyString)")
    
    let kDateString = Data(dateSHA256).map { String(format: "%02hhx", $0) }.joined()
    print("kDate \t\t= \(kDateString)")
    
    let kRegionString = Data(regionSHA256).map { String(format: "%02hhx", $0) }.joined()
    print("kRegion \t= \(kRegionString)")
    
    let kServiceString = Data(serviceNameSHA256).map { String(format: "%02hhx", $0) }.joined()
    print("kService \t= \(kServiceString)")
    
    let kSigningString = Data(signingSHA256).map { String(format: "%02hhx", $0) }.joined()
    print("kSigning \t= \(kSigningString)")
    

    Outputs

    kSecret     = 41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559
    kDate       = 969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d
    kRegion     = 69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c
    kService    = f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa
    kSigning    = f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d