Amazon consistently generates a different hash than PHP or CF, which causes a persistent "SignatureDoesNotMatch" error.
According to the docs, GET requests [without REST headers] are signed as follows:
Signature = URL-Encode( Base64( HMAC-SHA1( SecretAccessKey, UTF-8-Encoding-Of( StringToSign ) ) ) );
StringToSign = HTTP-VERB + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Expires + "\n" +
CanonicalizedAmzHeaders +
CanonicalizedResource;
The example data:
Two examples are provided:
To recreate this (CFHMAC from here):
// PHP
$expires = 1175139620;
$SecretAccessKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
$StringToSign = "GET\n\n\n$expires\n/johnsmith/photos/puppy.jpg";
$signature = urlencode( base64_encode( hash_hmac('sha1', utf8_encode($StringToSign), $SecretAccessKey, true)));
// ColdFusion
<cfset LF = chr(10)>
<cfset expires = 1141889120>
<cfset SecretAccessKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY">
<cfset StringToSign = "GET#LF##LF##LF##expires##LF#/johnsmith/photos/puppy.jpg">
<cfset signature = URLEncodedFormat( CFHMAC(StringToSign, SecretAccessKey))>
EXCEPT that $signature as returned by both languages is:
We have been careful of these gotchas that others have mentioned:
EDIT: Updated the newlines in the CF code based on Leigh's answer; now the CF matches the PHP.
I am obviously doing something wrong, but can't figure out what.
[I have heard it quipped that Amazon S3 would have been called CSS - "complicated storage service", but the name was already taken!]
Help, please!
(May as well post this since I had already written it up .. :)
Two problems I can see
LF
rather than a literal "\n" The result below matches that in Authentication Examples ie bWq2s1WEIj+Ydj0vQ697zp+IXMU=
. Note: I used the hmacSHA1 function from here, but changed it use getBytes("UTF-8)
Code:
<cfset newLine = chr(10)>
<cfset secretAccessKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY">
<cfset stringToSign = "GET#newLine##newLine##newLine#Tue, 27 Mar 2007 19:36:42 +0000#newLine#/johnsmith/photos/puppy.jpg">
<cfset signature = hmacSHA1(secretAccessKey, stringToSign)>
<cfset finalSignature = URLEncodedFormat(binaryEncode(signature, "base64"))>
<cfoutput>finalSignature = #finalSignature#</cfoutput>
****EDIT 1:**
Something is fishy. Most all of the examples on that page match up. But REST Authentication Example 3: Query String Authentication Example here shows a different key and string that produce the signature vjbyPxybdZaNmGa%2ByT272YEAiv4%3D
. If you use those values in CF you do get the same signature. So I am wondering if it might just be a documentation error?
<cfset secretAccessKey = "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV">
<cfset stringToSign = "GET#newLine##newLine##newLine#1141889120#newLine#/quotes/nelson">
** EDIT 2:
I am pretty sure the REST examples are wrong. A search turned up this link containing yet another sample key. If you substitute that in the CF code, the signature is what you expected: rucSbH0yNEcP9oM2XNlouVI3BH4%3D
.
<cfset secretAccessKey = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o">
<cfset stringToSign = "GET#newLine##newLine##newLine#1175139620#newLine#/johnsmith/photos/puppy.jpg">