虽然亚马逊提供了sdk,使用SDK会自动帮你计算签名。如果你能使用SDK 请一定使用SDK。
但是目标平台为嵌入式平台(非Linux)只支持C接口,很多库移植成本过高,为了使用S3服务,我们应该首选restfulAPI 这种方案。这就涉及到了自己实现AWS V4签名的问题。
前排资料:
我们可以阅读AWS SDK(nodejs) 源码签名实现进行参考验证:
aws-nodejs-sample\node_modules\aws-sdk\lib\signers 及aws-nodejs-sample\node_modules\aws-sdk\libutil.js
AWSV4签名文档:
https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/sig-v4-authenticating-requests.html
与Amazon S3的每次交互都是经过身份验证。本节介绍使用AWS Signature Version 4算法的请求认证。S3目前只支持V4验证。
HTTP授权标头 - 使用HTTP Authorization标头是验证Amazon S3请求的最常用方法。所有Amazon S3 REST操作(使用POST请求的基于浏览器的上传除外)都需要此标头。本文使用的是这种方式。且为单个区块传输方式。
在单个区块中传输有效负载(AWS签名版本4)
当在单个块中传输有效载荷时,可以选择将有效载荷散列包含在签名计算中,称为带符号有效载荷(如果不包含它,有效载荷将被视为无符号)。即是否计算待上传文件hash值。
我们选择的是第二种:无符号有效负载选项 - UNSIGNED-PAYLOAD在构建规范请求时包含文字字符串,并x-amz-content-sha256在向S3发送请求时将相同的值设置为 标题值。不计算负载hash值。即x-amz-content-sha256 设置为UNSIGNED-PAYLOAD。
签名环境总结:
1 用于s3
2 单个区块上传文件
3 不计算有效负载hash
要计算签名,您首先需要一个字符串来签名。然后HMAC-SHA256使用签名密钥计算要签名的字符串的散列。下图说明了该过程,包括您为签名创建的字符串的各个组件
当Amazon S3收到验证请求时,它会计算签名,然后将其与您在请求中提供的签名进行比较。因此,您必须使用Amazon S3使用的相同方法来计算签名。以协议形式签署请求的过程称为规范化。
即你的操作必须完全符合标准,因为AWS 也会按照标准计算一次,相同就成功。
签名流程图:
所需要的基础方法:
我们可以从图中看出Authorization的得到有3部分组成,且是顺序获取 前一个是后一个的前置条件。
1 Canonical Request
2 StringToSign
3 Signature
下面将分别介绍
本文验证用了些全局变量:
var accessKeyId = ' ';
var secretAccessKey= ' ';
var region='us-east-1';
var serviceName='s3';
var algorithm = 'AWS4-HMAC-SHA256';
var v4Identifier = 'aws4_request';
Canonitcal Requet 有下面六个部分组成
<HTTPMethod>\n
<CanonicalURI>\n
<CanonicalQueryString>\n
<CanonicalHeaders>\n
<SignedHeaders>\n
<HashedPayload>
HTTPMethod 是HTTP方法之一,例如GET,PUT,HEAD和DELETE。
CanonicalURI是URI的绝对路径组件的URI编码版本 - 任何以跟在域名后面的“/”开头,直到字符串末尾或问号字符。
如http://s3.amazonaws.com/examplebucket/myphoto.jpg ,CanonicalURI即为/examplebucket/myphoto.jpg 。
CanonicalQueryString指定URI编码的查询字符串参数。您可以单独对URI进行编码名称和值。即url的query参数。比较复杂,详情可参见文档。
因为我们是S3操作不带query参数,按文档说明:
如果URI不包含’?’,则请求中没有查询字符串,并且将规范查询字符串设置为空字符串(“”)。您仍然需要包含“\ n”。
CanonicalHeaders是请求标题及其值的列表。单独的标题名称和值对由换行符(“\ n”)分隔。标题名称必须小写。您必须按字母顺序排列标题名称以构建字符串
Lowercase()+":"+Trim()+"\n"
Lowercase()+":"+Trim()+"\n"
...
Lowercase()+":"+Trim()+"\n"
sample:
'content-md5:ZwmeZ+R9fiKB4KNgjnCUbw==\nhost:xxxxxx.s3.amazonaws.com\nx-amz-content-sha256:UNSIGNED-PAYLOAD\nx-amz-date:20180313T054459Z\n'
实现code:
function canonicalHeaderValues(values) {
return values.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '');
}
function canonicalHeaders(request) {
var headers = [];
for(var i in request.header)
{
headers.push([i, request.header[i]]);
}
headers.sort(function (a, b) {
return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1;
});
var parts = [];
for(var i in headers)
{
var key = headers[i][0].toLowerCase();
var value = headers[i][1];
parts.push(key + ':' + canonicalHeaderValues(value.toString()));
}
return parts.join('\n');
}
HashedPayload 是请求有效负载的SHA256哈希值的十六进制值。我们选择不计算有效负载hash值 直接给 UNSIGNED-PAYLOAD
function canonicalString(request) {
var parts = [];
//pathname = uriEscapePath(request.pathname);
parts.push(request.method);
parts.push(request.pathname);
parts.push("");
parts.push(canonicalHeaders(request) + '\n');
parts.push(signedHeaders(request));
parts.push('UNSIGNED-PAYLOAD');
console.info(parts);
return parts.join('\n');
}
Request 参数
var header ={
'host' : 'xxxxxx.s3.amazonaws.com',
'x-amz-content-sha256':'UNSIGNED-PAYLOAD',
'x-amz-date':date,
'content-md5':md5
}
var req ={
header : header,
pathname : '/xxxx/test02.jpg',
method : 'PUT',
bodyhash: 'UNSIGNED-PAYLOAD'
}
date参数:
var date = (new Date()).toISOString().replace(/\-|:|\.\d\d\d/g,""); (后面所有的date都是这个)
20180313T054459Z (注意时区)
Md5参数:
const hash = crypto.createHash('md5');
hash.update(bitmap);
var md5 = hash.digest('base64');
console.info(md5);
打印结果( console.info(parts)
[ 'PUT',
'/xxxx/test02.jpg',
'',
'content-md5:ZwmeZ+R9fiKB4KNgjnCUbw==\nhost:xxxxxx.s3.amazonaws.com\nx-amz-content-sha256:UNSIGNED-PAYLOAD\nx-amz-date:20180313T054459Z\n',
'content-md5;host;x-amz-content-sha256;x-amz-date',
'UNSIGNED-PAYLOAD' ]
本节概述创建要签名的字符串。对于一步一步的说明,请参阅任务2:创建一个字符串来登录在AWS一般参考。
要签名的字符串是以下字符串的串联:
"AWS4-HMAC-SHA256" + "\n" +
timeStampISO8601Format + "\n" +
+ "\n" +
Hex(SHA256Hash())
常量字符串AWS4-HMAC-SHA256指定您正在使用的哈希算法HMAC-SHA256
这timeStamp是ISO 8601格式的当前UTC时间(例如, 20130524T000000Z)
前面我们已经获取到了
Scope将生成的签名绑定到特定日期,AWS区域和服务
sample:
20180313/us-east-1/s3/aws4_request
code:
function createScope(date, region, serviceName) {
return [
date.substr(0, 8),
region,
serviceName,
'aws4_request'
].join('/');
}
function credentialString(datetime) {
return createScope(
datetime.substr(0, 8),
region,
serviceName
);
}
将2.1中得到的CanonicalRequest 进行sha256 在转成16进制
code:
sha256(string, 'hex'); //基础方法已经提供
Code:
function stringToSign(datetime, request) {
var parts = [];
parts.push('AWS4-HMAC-SHA256');
parts.push(datetime);
parts.push(credentialString(datetime));
console.info(canonicalString(request));
parts.push(hexEncodedHash(canonicalString(request)));
console.info(parts);
return parts.join('\n');
}
打印结果:
[ 'AWS4-HMAC-SHA256',
'20180313T054459Z',
'20180313/us-east-1/s3/aws4_request',
'1ae0b089a24ef398ef1204efe29fee5b7c762e43f35165b0c4bc74xxxxxxxxx' ]
我们可以在图中看出获得Signature 需要signingKey和StringToSign。
HMAC-SHA256(SigningKey, StringToSign)
在AWS签名版本4中,不是使用您的AWS访问密钥签署请求,而是首先创建一个签名密钥,该密钥的范围限定为特定的区域和服务。
签名密匙得到:
DateKey = HMAC-SHA256("AWS4"+"" , "" )
DateRegionKey = HMAC-SHA256(, "" )
DateRegionServiceKey = HMAC-SHA256(, "" )
SigningKey = HMAC-SHA256(, "aws4_request")
code:
function getSigningKey(credentials,date,region,service,shouldCache)
{
var kDate =hmac(
'AWS4' + secretAccessKey,
date,
'buffer'
);
var kRegion = hmac(kDate, region, 'buffer');
var kService = hmac(kRegion, service, 'buffer');
var signingKey = hmac(kService, v4Identifier, 'buffer');
return signingKey;
}
HMAC-SHA256(SigningKey, StringToSign)
code:
function signature(datetime, request) {
var signingKey = getSigningKey(
0,
datetime.substr(0, 8),
region,
serviceName
);
return hmac(signingKey, stringToSign(datetime, request), 'hex');
}
打印结果:
6553bf9cab10de458afb093543b8154903307ec96a7e2d84696c38941aaad1aa
Authorization由下列值组成:
Authorization: AWS4-HMAC-SHA256
Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,
SignedHeaders=host;range;x-amz-date, ( Canonitcal Requet 已获取)
Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024 (已得到)
Code:
function authorization(request, datetime) {
var parts = [];
var credString = credentialString(datetime);
parts.push('AWS4-HMAC-SHA256'+ ' Credential=' +accessKeyId + '/' + credString);
parts.push('SignedHeaders=' + signedHeaders(request));
parts.push('Signature=' + signature(datetime, request));
return parts.join(', ');
}
打印结果:
AWS4-HMAC-SHA256 Credential=XXXXXXX/20180313/us-east-1/s3/aws4_request, SignedHeaders=content-md5;host;x-amz-content-sha256;x-amz-date, Signature=6553bf9cab10de458afb093543b8154903307ec96a7e2d84696c3xxxxxxxxx
var options = {url: 'http://xxxxxxx.s3.us-east-1.amazonaws.com/xxxxx/test02.jpg',
headers: {
'content-md5':md5 ,
host: 'xxxxxxx.s3.amazonaws.com',
'x-amz-content-sha256':'UNSIGNED-PAYLOAD',
'x-amz-date':date,
Authorization: result
},
body:bitmap
};
request.put(options, function(error, response, body ) {
console.log( 'response: ' + response );
console.log( 'body: ' + body );
});