使用HTTP POST上传AWS S3浏览器会产生无效签名

我正在一个用户应该能够将video文件上传到AWS的网站上工作。 为了避免不必要的流量,我希望用户直接上传到AWS(而不是通过API服务器)。 为了不在JavaScript中公开我的秘密密钥,我试图在API中生成签名。 但是,它确实告诉我,当我尝试上传时,签名不匹配。

对于签名生成,我一直在使用http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html

在后端我正在运行C#。

我使用生成签名

string policy = $@"{{""expiration"":""{expiration}"",""conditions"":[{{""bucket"":""dennisjakobsentestbucket""}},[""starts-with"",""$key"",""""],{{""acl"":""private""}},[""starts-with"",""$Content-Type"",""""],{{""x-amz-algorithm"":""AWS4-HMAC-SHA256""}}]}}"; 

产生以下内容

 {"expiration":"2016-11-27T13:59:32Z","conditions":[{"bucket":"dennisjakobsentestbucket"},["starts-with","$key",""],{"acl":"private"},["starts-with","$Content-Type",""],{"x-amz-algorithm":"AWS4-HMAC-SHA256"}]} 

基于http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html (我对base64编码策略)。 我试图保持它非常简单,只是作为一个起点。

为了生成签名,我使用AWS站点上的代码。

 static byte[] HmacSHA256(String data, byte[] key) { String algorithm = "HmacSHA256"; KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm); kha.Key = key; return kha.ComputeHash(Encoding.UTF8.GetBytes(data)); } static byte[] GetSignatureKey(String key, String dateStamp, String regionName, String serviceName) { byte[] kSecret = Encoding.UTF8.GetBytes(("AWS4" + key).ToCharArray()); byte[] kDate = HmacSHA256(dateStamp, kSecret); byte[] kRegion = HmacSHA256(regionName, kDate); byte[] kService = HmacSHA256(serviceName, kRegion); byte[] kSigning = HmacSHA256("aws4_request", kService); return kSigning; } 

我用的是这样的:

 byte[] signingKey = GetSignatureKey(appSettings["aws:SecretKey"], dateString, appSettings["aws:Region"], "s3"); byte[] signature = HmacSHA256(encodedPolicy, signingKey); 

其中dateString的格式为yyyymmdd

我用JavaScript发布信息

 let xmlHttpRequest = new XMLHttpRequest(); let formData = new FormData(); formData.append("key", ""); formData.append("acl", signature.acl); // private formData.append("Content-Type", "$Content-Type"); formData.append("AWSAccessKeyId", signature.accessKey); formData.append("policy", signature.policy); //base64 of policy formData.append("x-amz-credential", signature.credentials); // /20161126/eu-west-1/s3/aws4_request formData.append("x-amz-date", signature.date); formData.append("x-amz-algorithm", "AWS4-HMAC-SHA256"); formData.append("Signature", signature.signature); formData.append("file", file); xmlHttpRequest.open("post", "http://.s3-eu-west-1.amazonaws.com/"); xmlHttpRequest.send(formData); 

我一直按照AWS的规定使用UTF8。 在他们的例子中,签名是hex格式,我也尝试过。 无论我尝试什么,我都会收到错误403

 The request signature we calculated does not match the signature you provided. Check your key and signing method. 

我在AWS上的政策有“s3:Get *”,“s3:Put *”

我是否遗漏了某些东西,或者它的工作方式与我的期望完全不同?

编辑:下面的答案是其中一个步骤。 另一个是AWS区分大写和小写六角形字符串。 在AWS眼中,0xFF!= 0xff。 他们想要全部小写的签名。

您正在使用签名版本4生成签名,但是您正在构建表单,就好像您使用的是签名版本2 …好吧,等等。

 formData.append("AWSAccessKeyId", signature.accessKey); 

那是V2。 它根本不应该在这里。

 formData.append("x-amz-credential", signature.credentials); // /20161126/eu-west-1/s3/aws4_request 

这是V4。 请注意此处及以上的AWS Access Key ID的冗余提交。 这个可能是正确的,虽然这些例子像X-Amz-Credential一样大写。

 formData.append("x-amz-algorithm", "AWS4-HMAC-SHA256"); 

这也是正确的,除了它可能需要是X-Amz-Algorithm 。 (这个例子似乎暗示大写被忽略了)。

 formData.append("Signature", signature.signature); 

这个是不正确的。 这应该是X-Amz-Signature 。 V4签名是hex的,所以这就是你应该拥有的。 V2签名是base64。

这里有一个完整的V4示例,它甚至为您提供了示例aws密钥和秘密,日期,区域,存储桶名称等,您可以将其与代码一起使用以validation您确实获得了相同的响应。 表单实际上不起作用,但重要的问题是您的代码是否可以生成相同的表单,策略和签名。

对于任何给定的请求,只有一个正确的签名; 但是,对于任何给定的策略,可能存在多个有效的JSON编码(由于JSON的空白灵活性) – 但对于任何给定的JSON编码,只有一个可能的有效base64编码的策略。 这意味着,如果代码生成与示例中显示的完全相同的表单和签名,则使用示例数据的代码被认证为正常工作 – 这意味着如果代码生成相同的表单和策略,则certificate代码无效一个不同的签名 – 但还有第三种可能性:如果您的代码生成不同的base64编码,那么测试实际上certificate您的代码没有任何结论,因为这必然会将签名更改为不匹配,但可能仍然有效政策。

请注意,Signature V2仅支持较旧的S3区域,而所有 S3区域都支持Signature V4,因此,即使您可以通过使整个签名过程使用V2来交替修复此问题,也不建议这样做。

另请注意, The request signature we calculated does not match the signature you provided. Check your key and signing method The request signature we calculated does not match the signature you provided. Check your key and signing method不会告诉您有关存储桶策略或任何用户策略是允许还是拒绝请求的任何信息。 此错误不是权限错误。 它将在权限检查之前抛出,仅基于签名的有效性,而不是AWS Access Key id是否被授权执行所请求的操作,这仅在签名validation后进行测试。

我建议你创建一个只有POST权限的一对身份validation令牌,并发送一个这样的http请求:

 require 'rest-client' class S3Uploader def initialize @options = { aws_access_key_id: "ACCESS_KEY", aws_secret_access_key: "ACCESS_SECRET", bucket: "BUCKET", acl: "private", expiration: 3.hours.from_now.utc, max_file_size: 524288000 } end def fields { :key => key, :acl => @options[:acl], :policy => policy, :signature => signature, "AWSAccessKeyId" => @options[:aws_access_key_id], :success_action_status => "201" } end def key @key ||= "temp/${filename}" end def url "http://#{@options[:bucket]}.s3.amazonaws.com/" end def policy Base64.encode64(policy_data.to_json).delete("\n") end def policy_data { expiration: @options[:expiration], conditions: [ ["starts-with", "$key", ""], ["content-length-range", 0, @options[:max_file_size]], { bucket: @options[:bucket] }, { acl: @options[:acl] }, { success_action_status: "201" } ] } end def signature Base64.encode64( OpenSSL::HMAC.digest( OpenSSL::Digest.new("sha1"), @options[:aws_secret_access_key], policy ) ).delete("\n") end end uploader = S3Uploader.new puts uploader.fields puts uploader.url begin RestClient.post(uploader.url, uploader.fields.merge(file: File.new('51bb26652134e98eae931fbaa10dc3a1.jpeg'), :multipart => true)) rescue RestClient::ExceptionWithResponse => e puts e.response end