来自PHP的错误SHA1;根据亚马逊S3的CF


Wrong SHA1 from PHP & CF according to 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和内容类型:(可选-跳过)
  • CanonicalizedAmzHeaders:(没有标头-已跳过)
  • 资源:johnsmith.s3.amazonaws.com/photos/puppy.jpg
  • CanonicalizedResource:/johnsmith/photes/pudge.jpg

提供了两个示例:

  1. 过期11175139620;签名:rucSbH0yNEcP9oM2XNlouVI3BH4%3D
  2. 过期11141889120;签名: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. 过期11175139620;签名:NpgCjnDzrM%2BWZoENXmpNDUSSn8%3D
  2. 过期11141889120;签名:fScKGHCDI0NY5E7CYp9Vc8VKMbY%3D

我们一直很小心其他人提到的这些陷阱:

  1. hash_mac有第三个参数raw,必须将其设置为true
  2. S3伪码中字符串ToSign和密钥的顺序应该颠倒
  3. 整个字符串ToSign必须在一行上(以免创建额外的换行符)

编辑:根据Leigh的回答更新CF代码中的换行符;现在CF与PHP匹配。

我显然做错了什么,但不知道是什么
[我听说有人打趣说,亚马逊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身份验证示例3:这里的查询字符串身份验证示例显示了生成签名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/

:)