0%

AWS-SignV4签名的学习笔记


概述

这里对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
2
3
4
5
6
7
CanonicalRequest =
HTTPRequestMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HexEncode(Hash(RequestPayload))

其中各个字段意思如下:

  • 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
2
3
4
content-length:11\n
host:example.com\n
x-amz-content-sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9\n
x-amz-date:20210511T080101Z\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

步骤二:构建StringToSign

1
2
3
4
5
StringToSign =
Algorithm + \n +
RequestDateTime + \n +
CredentialScope + \n +
HashedCanonicalRequest

其中各个字段意思如下:

  • 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
2
3
4
5
6
kSecret := []byte(Scheme + sk)
kDate := HmacSHA256([]byte(dateStamp), kSecret)
kRegion := HmacSHA256([]byte(regionName), kDate)
kService := HmacSHA256([]byte(serviceName), kRegion)
kSigning := HmacSHA256([]byte(Terminator), kService)
signature := HmacSHA256([]byte(stringToSign), kSigning)

这里sk就是密钥。

这里使用的ak,sk值为:

1
2
ak: A7GqwejrKHkJ7K8Tz88u
sk: teFxGLlckz8d1AzzhSTxBhXPIQ7Qq06yAm77SM3M

可以看到经过多次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]

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签名和预签名进行鉴权。

https://github.com/isadamu/aws-signature-v4