使用cURL/libcurl上传到Amazon S3

7

我目前正在尝试使用cURL和c++开发一个上传文件到Amazon S3存储桶的应用程序。经过仔细阅读S3开发者指南后,我已经开始使用cURL并按照开发者指南中描述的方式形成Header,在经历了多次尝试和错误后,确定了创建S3签名的最佳方法,但是现在我遇到了501错误。接收到的头部提示我使用的方法未实现。我不确定我的错误在哪里,但以下是我发送给亚马逊的HTTP头部:

PUT /test1.txt HTTP/1.1
Accept: */*
Transfer-Encoding: chunked
Content-Type: text/plain
Content-Length: 29
Host: [BucketName].s3.amazonaws.com 
Date: [Date]
Authorization: AWS [Access Key ID]:[Signature] 
Expect: 100-continue

出于安全原因,我已经省略了Bucket名称、Access Key ID和Signature。

我不确定自己做错了什么,但我认为错误是由于Accept和Transfer-Encoding字段(并非完全确定)而产生的。所以,有人能告诉我我错在哪里,或者为什么会得到一个501吗?


已解决:我的代码中缺少了一个CURLOPT文件大小设置,现在一切都完美地运行了。 - Zaid Amir
3个回答

7
自问题被提出以来,游戏发生了重大变化,简单的授权标头不再适用,但仍可通过UNIX shell脚本执行,如下所示。
确保在命令行中可用'openssl'和'curl'。提示:请仔细检查openSSL参数语法,因为这些可能因工具的不同版本而有所不同;例如openssl sha -sha256 ...与openssl sha256 ...。
注意,单个额外的换行符或空格字符,否则使用CRLF代替NewLine char将使签名失效。还要注意,您可能需要使用内容类型,可能带有编码,以防止通过通信媒体进行任何数据转换。然后,您可能需要在多个位置调整签名标头列表;请参考AMAZON S3 API docs以保持强制执行的众多约定,例如哈希计算中用于排序的字母小写头信息。
# BERHAUZ Nov 2019 - curl script for file upload to Amazon S3 Buckets
test -n "$1" || {
  echo "usage: $0 <myFileToSend.txt>"
  echo "... missing argument file ..."
  exit
}
yyyymmdd=`date +%Y%m%d`
isoDate=`date --utc +%Y%m%dT%H%M%SZ`
# EDIT the next 4 variables to match your account
s3Bucket="myBucket.name.here"
bucketLocation="eu-central-1" 
s3AccessKey="THISISMYACCESSKEY123"
s3SecretKey="ThisIsMySecretKeyABCD1234efgh5678"

#endpoint="${s3Bucket}.s3-${bucketLocation}.amazonaws.com"
endpoint="s3-${bucketLocation}.amazonaws.com"

fileName="$1"
contentLength=`cat ${fileName} | wc -c`
contentHash=`openssl sha256 -hex ${fileName} | sed 's/.* //'`

canonicalRequest="PUT\n/${s3Bucket}/${fileName}\n\ncontent-length:${contentLength}\nhost:${endpoint}\nx-amz-content-sha256:${contentHash}\nx-amz-date:${isoDate}\n\ncontent-length;host;x-amz-content-sha256;x-amz-date\n${contentHash}"
canonicalRequestHash=`echo -en ${canonicalRequest} | openssl sha256 -hex | sed 's/.* //'`

stringToSign="AWS4-HMAC-SHA256\n${isoDate}\n${yyyymmdd}/${bucketLocation}/s3/aws4_request\n${canonicalRequestHash}"

echo "----------------- canonicalRequest --------------------"
echo -e ${canonicalRequest}
echo "----------------- stringToSign --------------------"
echo -e ${stringToSign}
echo "-------------------------------------------------------"

# calculate the signing key
DateKey=`echo -n "${yyyymmdd}" | openssl sha256 -hex -hmac "AWS4${s3SecretKey}" | sed 's/.* //'`
DateRegionKey=`echo -n "${bucketLocation}" | openssl sha256 -hex -mac HMAC -macopt hexkey:${DateKey} | sed 's/.* //'`
DateRegionServiceKey=`echo -n "s3" | openssl sha256 -hex -mac HMAC -macopt hexkey:${DateRegionKey} | sed 's/.* //'`
SigningKey=`echo -n "aws4_request" | openssl sha256 -hex -mac HMAC -macopt hexkey:${DateRegionServiceKey} | sed 's/.* //'`
# then, once more a HMAC for the signature
signature=`echo -en ${stringToSign} | openssl sha256 -hex -mac HMAC -macopt hexkey:${SigningKey} | sed 's/.* //'`

authoriz="Authorization: AWS4-HMAC-SHA256 Credential=${s3AccessKey}/${yyyymmdd}/${bucketLocation}/s3/aws4_request, SignedHeaders=content-length;host;x-amz-content-sha256;x-amz-date, Signature=${signature}"

curl -v -X PUT -T "${fileName}" \
-H "Host: ${endpoint}" \
-H "Content-Length: ${contentLength}" \
-H "x-amz-date: ${isoDate}" \
-H "x-amz-content-sha256: ${contentHash}" \
-H "${authoriz}" \
http://${endpoint}/${s3Bucket}/${fileName}

我必须承认,对于像我这样涉足密码学的人来说,Amazon签名方案值得受到许多批评:
  • 签名信息中存在很多冗余。
  • 5步HMAC级联几乎颠倒了密钥种子和数据之间的语义,如果使用正确且安全性相同,则只需要1步。
  • 秘密密钥的最后12个字符在此处是无用的,因为SHA256 HMAC的有效密钥长度为256位,即32字节,其中前4个始终以"AWS4"开头,没有任何目的。
  • 总体而言,AWS S3 API重新发明了标准,S/MIME有效载荷则可以胜任。

对于我的批评,我表示歉意,但我无法抵制。然而,必须承认:它可靠地工作,对许多公司有用,并且是一个具有丰富API的有趣服务。


我已经写了一半,然后决定再用一个关键词搜索,结果找到了这个答案。到目前为止,这是我找到的最接近的答案,尽管似乎签名方案又改变了,因为亚马逊正在响应“我们计算的请求签名与您提供的签名不匹配。”感谢你为我们其他人解决了这个问题! - gelliott181
1
"signature calculated does not match" 表示“规范请求”或“签名字符串”与实际的HTTP头/数据之间存在差异。在处理文件路径时要非常小心(AWS3仅知道存储桶的PUT路径),并且还要考虑请求头在“规范请求”和“签名字符串”中的列表之间的最严格对齐。使用curl -v(详细)查看HTTP请求中的确切标头和路径。此外,如果您添加了内容类型,则必须调整“规范请求”和“签名字符串”中的标头列表,并按照其API文档中记录的方式进行“规范化”。 - Bernard Hauzeur
我遇到了以下错误:来自AWS的“授权标头格式不正确;无效的凭证日期。日期与X-Amz-Date不同”。这是它的键/值对:“x-amz-date: 20201025T020215Z”。 - Sergey V.
我认为(但不确定)在提交HTTP请求时会插入一个“日期”头,该头基于您的本地机器时区,并且此头值与生成的x-amz-date日期发生冲突...还要确保您的机器时间与官方时间足够对齐。 - Bernard Hauzeur
<Error><Code>InvalidRequest</Code><Message>Missing required header for this request: x-amz-content-sha256</Message - Matt
这很明显:仔细查看脚本,缺失的头文件应该被构建。因此,您很可能有一些脚本部分未按预期执行。检查shell exec环境(特别是在您的shell中echo命令的行为)并追踪可能已经滑入的不良字符。使用curl -v参数添加跟踪到交换。像上面那样级联加密操作本质上是脆弱的。 - Bernard Hauzeur

6
您可以执行一个bash文件。这里是一个例子upload.sh脚本,您可以直接运行:sh upload.sh yourfile
#!/bin/bash
file=$1
bucket=YOUR_BUCKET
resource="/${bucket}/${file}"
contentType="application/x-itunes-ipa"
dateValue=`date -R`
stringToSign="PUT\n\n${contentType}\n${dateValue}\n${resource}"
s3Key=YOUR_KEY_HERE
s3Secret=YOUR_SECRET
echo "SENDING TO S3"
signature=`echo -en ${stringToSign} | openssl sha1 -hmac ${s3Secret} -binary | base64` 
curl -vv -X PUT -T "${file}" \
 -H "Host: ${bucket}.s3.amazonaws.com" \
 -H "Date: ${dateValue}" \
 -H "Content-Type: ${contentType}" \
 -H "Authorization: AWS ${s3Key}:${signature}" \
 https://${bucket}.s3.amazonaws.com/${file}

更多内容请查看:http://www.jamesransom.net/?p=58

http://www.jamesransom.net/?p=58


谢谢,但这个问题是五年前的了,正如标签所示,它是关于Windows的。 - Zaid Amir
在nix上,您可以执行contentType="$(file -b --mime-type "$1")" - jchook

-8
已解决:我的代码缺少了一个CURLOPT文件大小参数,现在一切都完美地运行了。

6
什么选项?标题头是怎样改变的? - Nick Retallack
3
我不明白为什么有些人会用"我已经解决了,但我不会告诉你我是怎么解决的"这样的回答来回应别人的帮助。请详细说明你的解决方法,否则会浪费别人的时间。 - Jimmy Johnson
这个答案没有揭示用户Zaid Amir所应用的解决方案的任何解释。看起来他在自言自语。 - Fernando Aspiazu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接