Неправильный SHA1 от PHP и CF в соответствии с Amazon S3

Amazon последовательно генерирует другой хэш, чем PHP или CF, что вызывает постоянную ошибку SignatureDoesNotMatch.

Согласно документам , запросы GET [без заголовков REST] подписаны следующим образом:

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; 

Примеры данных:

  • SecretAccessKey: wJalrXUtnFEMI / K7MDENG / bPxRfiCYEXAMPLEKEY
  • Content-MD5 и Content-Type: (необязательно – пропущено)
  • CanonicalizedAmzHeaders: (нет заголовков – пропущено)
  • Ресурс: johnsmith.s3.amazonaws.com/photos/puppy.jpg
  • CanonicalizedResource: /johnsmith/photos/puppy.jpg

Предоставляются два примера:

  1. Истекает 1175139620 ; Подпись: rucSbH0yNEcP9oM2XNlouVI3BH4% 3D
  2. Истекает 1141889120 ; Подпись: vjbyPxybdZaNmGa% 2ByT272YEAiv4% 3D

Чтобы воссоздать это (CFHMAC отсюда ):

 // 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))> 

ЗА ИСКЛЮЧЕНИЕМ, что $ подпись, возвращаемая обоими языками:

  1. Истекает 1175139620 ; Подпись: NpgCjnDzrM% 2BWFzoENXmpNDUsSn8% 3D
  2. Истекает 1141889120 ; Подпись: fScKGHCDI0NY5E7CYp9Vc8VKMbY% 3D

Мы были осторожны с этими пропастями, о которых говорили другие:

  1. hash_mac имеет третий аргумент, raw, который должен быть установлен в true.
  2. Порядок stringToSign и ключ в псевейкоде S3 должны быть отменены.
  3. Весь stringToSign должен быть в одной строке (чтобы не создавать дополнительные символы новой строки).

EDIT: Обновлены новые строки в CF-коде, основанные на ответе Ли; теперь CF соответствует PHP.

Я, очевидно, что-то делаю неправильно, но не могу понять, что.
[Я слышал, что он сказал, что Amazon S3 можно было бы назвать CSS – «сложным сервисом хранения», но имя уже было принято!]

Помоги пожалуйста!

(Может также опубликовать это, так как я уже написал это … 🙂

Две проблемы, которые я вижу

  1. Дата должна быть отформатирована определенным образом
  2. Вам нужно использовать LF а не буквальный \ n "

Результат ниже соответствует тому, что в примерах аутентификации, т.е. bWq2s1WEIj+Ydj0vQ697zp+IXMU= . Примечание. Я использовал функцию hmacSHA1 здесь , но изменил ее с помощью getBytes("UTF-8)

Код:

  <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> 

**** ИЗМЕНИТЬ 1: **

Что-то подозрительное. Большинство примеров на этой странице совпадают. Но REST Authentication Example 3: Query String Authentication Пример здесь показывает другой ключ и строку, которые создают подпись vjbyPxybdZaNmGa%2ByT272YEAiv4%3D . Если вы используете эти значения в CF, вы получаете одну и ту же подпись. Поэтому мне интересно, может ли это быть ошибка документации?

  <cfset secretAccessKey = "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"> <cfset stringToSign = "GET#newLine##newLine##newLine#1141889120#newLine#/quotes/nelson"> 

** ИЗМЕНИТЬ 2:

Я уверен, что примеры REST ошибочны. Поиск показал эту ссылку, содержащую еще один образец ключа. Если вы замените это в CF-коде, подпись будет тем, что вы ожидали: rucSbH0yNEcP9oM2XNlouVI3BH4%3D .

  <cfset secretAccessKey = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"> <cfset stringToSign = "GET#newLine##newLine##newLine#1175139620#newLine#/johnsmith/photos/puppy.jpg"> 

Это поможет?

 <cffunction name="getRequestSignature" access="private" output="false" returntype="string"> <cfargument name="verb" type="string" required="true" /> <cfargument name="bucket" type="string" required="true" /> <cfargument name="objectKey" type="string" required="true" /> <cfargument name="dateOrExpiration" type="string" required="true" /> <cfargument name="contentType" type="string" default="" /> <cfargument name="contentMd5" type="string" default="" /> <cfargument name="canonicalizedAmzHeaders" type="string" default="" hint="A newline-delimited list of headers, in lexographical order, duplicates collapsed, and no extraneous whitespace. See Amazon's description of 'CanonicalizedAmzHeaders' for specifics." /> <cfscript> var stringToSign = ""; var algo = "HmacSHA1"; var signingKey = ""; var mac = ""; var signature = ""; stringToSign = uCase(verb) & chr(10) & contentMd5 & chr(10) & contentType & chr(10) & dateOrExpiration & chr(10) & iif(len(canonicalizedAmzHeaders) GT 0, de(canonicalizedAmzHeaders & chr(10)), de('')) & "/" & listAppend(bucket, objectKey, "/"); signingKey = createObject("java", "javax.crypto.spec.SecretKeySpec").init(variables.awsSecret.getBytes(), algo); mac = createObject("java", "javax.crypto.Mac").getInstance(algo); mac.init(signingKey); signature = toBase64(mac.doFinal(stringToSign.getBytes())); return signature; </cfscript> </cffunction> 

Полностью украл его здесь: http://www.barneyb.com/barneyblog/projects/amazon-s3-cfc/

🙂