概述
这里对AWS-SignV4签名进行一下简单研究,如何对HTTP请求生成AWS-SignV4签名(包括Header签名和Url预签名),以及如何对一个请求进行AWS-SignV4鉴权。
这里主要参考了官方文档:
Signature Version 4 signing process
Authenticating Requests (AWS Signature Version 4)
我这里使用go写了一份代码:aws-signature-v4
AWS-SignV4签名步骤
header签名
假如现在PUT请求url为http://example.com/bucket1/test.txt?aa=123&Ab
,body为hello world
。
那么上述的请求进行签名的结果如下:
步骤一:构建Canonical request
首先需要构建Canonical request
:
1 | CanonicalRequest = |
其中各个字段意思如下:
- HTTPRequestMethod:请求的Method,例如GET、POST等。
- CanonicalURI:http请求path,例如/abc/c.txt。
- CanonicalQueryString:经过处理的所有的query参数。
- CanonicalHeaders:经过处理的加入签名的header参数。
- SignedHeaders:加入签名的header参数的所有key。
- HexEncode(Hash(RequestPayload)):http负载(也就是body)的hash值。
HTTPRequestMethod
1 | PUT |
请求为PUT
,所以这里取值也为PUT
。
CanonicalURI
1 | /bucket1/test.txt |
请求path为/bucket1/test.txt
,所以值也为/bucket1/test.txt
。
CanonicalQueryString
1 | Ab=&aa=123 |
首先这里的Query必需包含请求URL上的所有Query参数,
query为aa=123&Ab
,这里Ab
没有值,那就默认其值为空。对键值进行区分大小写的字符串排序后,重新拼接得到Ab=&aa=123
。
所以这里取值为Ab=&aa=123
。
CanonicalHeaders
1 | content-length:11\n |
首先这里是PUT方法,向s3中写入一个test.txt
,其内容为hello world
,所以必需在签名中加入content-length
字段,内容长度为11字节。注意到,如果没有携带body,那么header签名时不要加上content-length
。
host
这里就是example.com
。
x-amz-content-sha256
是对body
进行sha256运算之后得到的hash值。注意,即使没有携带body,也就是body为空,也要在签名中加入此字段。
x-amz-date
是签名日期,注意这里是UTC时间,也就是0时区。
它们的顺序是对键值进行不区分大小写的排序得来的。
这里都是默认必备的字段,也可以加入自定义字段。
SignedHeaders
1 | content-length;host;x-amz-content-sha256;x-amz-date |
这里对应的就是CanonicalHeaders
,顺序与其保持一致。
HexEncode(Hash(RequestPayload))
1 | b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 |
body的sha256哈希值,进行HexEncode编码后得到。
注意,即使没有携带body,也就是body为空,也要在签名中加入此字段。
构建得到Canonical request
1
2
3
4
5
6
7
8
9
10
PUT
/bucket1/test.txt
Ab=&aa=123
content-length:11
host:example.com
x-amz-content-sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
x-amz-date:20210511T080101Z
content-length;host;x-amz-content-sha256;x-amz-date
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
1 | PUT |
步骤二:构建StringToSign
1 | StringToSign = |
其中各个字段意思如下:
- Algorithm:使用的hash算法。
- RequestDateTime:请求时间,与x-amz-date一致。
- CredentialScope:包含日期,区域,所请求服务。
- HashedCanonicalRequest:经过hash过后的
Canonical request
。
Algorithm
1 | AWS4-HMAC-SHA256 |
AWS4代表signV4,然后通常情况hash算法都选择的是HMAC-SHA256
,拼接到一起得到AWS4-HMAC-SHA256
。
RequestDateTime
1 | 20210511T080101Z |
请求时间,精确到秒数,格式固定,与x-amz-date日期一致。
CredentialScope
1 | 20210511/ep-east-1/s3/aws4_request |
20210511
即日期。
ep-east-1
代表区域,这个其实只有在真正使用aws服务的时候,才需要注意。
s3
表示s3服务。
aws4_request
为固定结束符。
拼到一起即为20210511/ep-east-1/s3/aws4_request
。
HashedCanonicalRequest
1 | f36e0e6979bec2c3d0f35e327eb74cc81da7de6f4ee23e8af99c64fff102a583 |
对第一步得到的CanonicalRequest
进行SHA256和Hex后得到,即Hex(SHA256(CanonicalRequest))
。
构建得到StringToSign
1
2
3
4
AWS4-HMAC-SHA256
20210511T080101Z
20210511/ep-east-1/s3/aws4_request
f36e0e6979bec2c3d0f35e327eb74cc81da7de6f4ee23e8af99c64fff102a583
1 | AWS4-HMAC-SHA256 |
步骤三:计算签名
这里贴出代码:
1 | kSecret := []byte(Scheme + sk) |
这里sk就是密钥。
这里使用的ak,sk值为:
1 | ak: A7GqwejrKHkJ7K8Tz88u |
可以看到经过多次hash后,即得到signature。
1 | 83e0f7e5cf34e103349b081d6ec5e5a91aa4e9cc68a2fd6c2f4fcdd077190986 |
之后再进行拼接才能得到header中的Authorization,
1 | AWS4-HMAC-SHA256 Credential=A7GqwejrKHkJ7K8Tz88u/20210511/ep-east-1/s3/aws4_request, SignedHeaders=content-length;host;x-amz-content-sha256;x-amz-date, Signature=83e0f7e5cf34e103349b081d6ec5e5a91aa4e9cc68a2fd6c2f4fcdd077190986 |
在请求header中加入Authorization以及其它加入签名的头部字段即可,如下:
1
2
3
4
5
6
Request headers:
Content-Length: [11]
x-amz-content-sha256 : [b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9]
x-amz-date : [20210511T080101Z]
Authorization : [AWS4-HMAC-SHA256 Credential=A7GqwejrKHkJ7K8Tz88u/20210511/ep-east-1/s3/aws4_request, SignedHeaders=content-length;host;x-amz-content-sha256;x-amz-date, Signature=83e0f7e5cf34e103349b081d6ec5e5a91aa4e9cc68a2fd6c2f4fcdd077190986]
Host : [example.com]
1 | Request headers: |
url签名
url签名,也就是预签名,这个通常用于GET请求,比如让别人可以通过一个链接在一天之内读取你的某个文件,一天之后链接失效,访问返回403。
假设现在GET请求url为http://example.com/bucket1/test.txt?aa=123&Ab
,body为hello world
。
得到预签名url如下:
1 | http://example.com/bucket1/test.txt?aa=123&Ab&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=A7GqwejrKHkJ7K8Tz88u/20210511/ep-east-1/s3/aws4_request&X-Amz-Date=20210511T095043Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=2e0e7bf17958b7347bac7cf39fddadddeadb0c792d8a1b2453b54f9f7678e9bb |
url的计算方法与header签名是一致的,不同的是,签名的大部分信息都放到了url上,通常header中只有一个host信息。
url签名不对body进行签名(同理content-length),bodyHash值直接强制取值为UNSIGNED-PAYLOAD
。
url签名需要加入一个X-Amz-Expires
信息,代表签名的有效时长,单位秒。
具体的东西这里不再赘述,可以在代码中查看。
AWS-SignV4签名代码
下面这个代码是我用golang简单写的,参考了一下aws的java版本demo代码。
可以进行header签名和预签名,也可以作为server,对header签名和预签名进行鉴权。