我正在一个网站上,用户应该可以在该网站上将视频文件上传到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区分大写和小写的十六进制字符串。在AWS看来0xFF!= 0xff 他们希望签名全部小写。
您正在使用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例如在这里,甚至提供了一个例子AWS密钥和密码,日期,区域,斗名称等,您可以与您的代码使用来验证你确实得到了同样的答复。该表单实际上无法正常工作,但重要的问题是您的代码是否可以生成相同的表单,策略和签名。
对于任何给定的请求,只有一个正确的签名。但是,对于任何给定的策略,可能有多个有效的JSON编码(由于JSON具有空格的灵活性),但是对于任何给定的JSON编码,该策略只有一种可能的有效base64编码。这意味着,如果您使用示例数据生成的代码和示例中生成的表单和签名完全相同,则您的代码将被证明可以正常工作;并且,如果您使用以下示例数据生成相同的表单和策略,则证明您的代码无效一个不同的签名-但是还有第三种可能性:如果您的代码生成了该策略的不同base64编码,则该测试实际上无法证明您的代码具有决定性,因为这必将使签名更改为不匹配,但仍然可能有效政策。
请注意,Signature V2仅支持在较旧的S3区域,而Signature V4受所有S3区域支持,因此,即使您可以通过使整个签名过程都使用V2来替代解决此问题,也不建议这样做。
另请注意,The request signature we calculated does not match the signature you provided. Check your key and signing method
这不会告诉您有关存储桶策略或任何用户策略是允许还是拒绝该请求的任何信息。此错误不是权限错误。仅在签名有效性的基础上,才会在权限检查之前抛出该错误,而不是是否授权AWS Access Key ID执行所请求的操作,只有在验证签名之后才进行测试。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句