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:
- SecretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
- Content-MD5 and Content-Type: (optional - skipped)
- CanonicalizedAmzHeaders: (no headers - skipped)
- Resource: johnsmith.s3.amazonaws.com/photos/puppy.jpg
- CanonicalizedResource: /johnsmith/photos/puppy.jpg
Two examples are provided:
- Expires 1175139620; Signature: rucSbH0yNEcP9oM2XNlouVI3BH4%3D
- Expires 1141889120; Signature: vjbyPxybdZaNmGa%2ByT272YEAiv4%3D
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:
- Expires 1175139620; Signature: NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D
- Expires 1141889120; Signature: fScKGHCDI0NY5E7CYp9Vc8VKMbY%3D
We have been careful of these gotchas that others have mentioned:
- hash_mac has a third argument, raw, which must be set to true.
- The order of the stringToSign and key in the S3 psuedocode should be reversed.
- The entire stringToSign must be on one line (so as not to create extra newline characters).
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!