【问题标题】:AWS S3 browser upload using HTTP POST gives invalid signature使用 HTTP POST 上传 AWS S3 浏览器给出了无效的签名
【发布时间】:2017-04-10 16:52:24
【问题描述】:

我正在开发一个用户应该能够将视频文件上传到 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", "<path-to-upload-location>");
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); // <accesskey>/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://<bucketname>.s3-eu-west-1.amazonaws.com/");
xmlHttpRequest.send(formData);

我一直在按照 AWS 的规定使用 UTF8。在他们的示例中,签名采用十六进制格式,我也尝试过。 无论我尝试什么,我都会收到错误 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区分大小写十六进制字符串。 0xFF != AWS 眼中的 0xff。他们想要全小写的签名。

【问题讨论】:

    标签: javascript c# amazon-web-services amazon-s3


    【解决方案1】:

    您正在使用 Signature Version 4 生成签名,但您正在构建表单,就好像您使用的是 Signature Version 2...嗯,有点。

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

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

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

    这是 V4。请注意此处和上方的 AWS 访问密钥 ID 的冗余提交。这一个可能是正确的,尽管这些示例具有像 X-Amz-Credential 这样的大写字母。

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

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

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

    这个不正确。这应该是X-Amz-Signature。 V4 签名是十六进制的,所以这就是你应该在这里拥有的。 V2 签名是 base64。

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

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

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

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

    【讨论】:

    【解决方案2】:

    我建议你创建一个仅对POST 有权限的身份验证令牌,然后发送这样的 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-24
      • 1970-01-01
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 1970-01-01
      相关资源
      最近更新 更多