-
Notifications
You must be signed in to change notification settings - Fork 361
SignV2
- OSS Signature Version 2
Compared with the previous signature version, OSS Signature version 2 has the following improvements:
- In OSS Signature Version 2, it uses SHA256 algorithm instead of SHA1 to sign the request, which has higher security.
- OSS Signature Version 2 includes all HTTP query parameters in signature calculation.
In following sections, we will describe the details of Signature Version 2. There are three functions that will be used in later sections:
-
UriEncode
to percentage encode a string -
Lowercase
to convert a string to lower case -
Trim
to return a copy of the input string with leading and trailing whitespaces removed
Below is a python implementation for UriEncode with input type of str
:
def UriEncode(raw_text):
"""raw_text is of type str."""
res = ''
for c in raw_text:
if (c >= 'A' and c <= 'Z') or (c >= 'a' and c <= 'z')\
or (c >= '0' and c <= '9') or c in ['_', '-', '~', '.']:
res += c
else:
res += "%{0:02X}".format(ord(c))
return res
An authorization header in Signature Version 2 has following form
Authorization: OSS2 AccessKeyId:<AccessKeyId>,Signature:<Signature>[,AdditionalHeaders:<AdditionalHeaders>]
where
-
OSS2
specifies the signature version -
<AccessKeyId>
is the AccessKeyId -
<AdditionalHeaders>
are headers included in signature calculation besides those prefixed byx-oss-
. It is a list of header names delimited by semi-colon. This field is optional. -
<Signature>
is the calculated signature, which will be verified by OSS.
There should be at least one space after OSS2
, and following OSS2 are comma separated key:value
list. Value of Authorization
header is case sensitive.
A sample authorization header is
authorization: OSS2 AccessKeyId:44CF9590006BF252F707,AdditionalHeaders:range;if-modified-since,Signature:YG9mKO3m4S0Jx9Hk6Lq64VchJg/TOTkyCX4DaeeOYxE=
where
-
<AccessKeyId>
is44CF9590006BF252F707
-
AdditionalHeaders
isrange;if-modified-since
-
Signature
isYG9mKO3m4S0Jx9Hk6Lq64VchJg/TOTkyCX4DaeeOYxE=
Following pseudo code illustrates how to calculate a signature
StringToSign =
VERB + "\n"
+ Content-MD5 + "\n"
+ Content-Type + "\n"
+ Date + "\n"
+ CanonicalizedOSSHeaders
+ AdditionalHeaders + "\n"
+ CanonicalizedResource
Signature = base64(hmac-sha256(AccessKeySecret, StringToSign))
where
-
VERB
is one of the HTTP methods, for example GET, PUT, HEAD, DELETE and POST. -
\n
is the newline character -
Content-MD5
is the value of request's Content-MD5 header, or empty string when Content-MD5 header is not present. -
Content-Type
is the value of request's Content-Type header, or empty string if Content-Type header is not present. -
Date
is the value of requests Data header. -
CanonicalizedOSSHeaders
contains headers prefixed byx-oss-
and additional headers -
AdditionalHeaders
is a list of normalized additional header names -
CanonicalizedResource
is the OSS resource to visit -
AccessKeySecret
is the AccessKeySecret
To construct the CanonicalizedOSSHeaders part of StringToSign
, select all HTTP request headers that start with x-oss-
(using a case-insensitive comparison) and headers in AdditionalHeaders
, and use the following process
- Convert each HTTP header's name to lower case and trim spaces leading and tailing whitespaces from its value.
- Sort headers by name in alphabetic order.
- Join each header's name and value by colon, then append a newline character.
- Construct the
CanonicalizedOSSHeaders
element by concatenating all headers in this list into a single string.
The resulted CanonicalizedOSSHeaders
is like
Lowercase(<HeaderName1>) + ":" + Trim(<HeaderValue1> + "\n"
Lowercase(<HeaderName2>) + ":" + Trim(<HeaderValue2> + "\n"
...
Lowercase(<HeaderNameN>) + ":" + Trim(<HeaderValueN> + "\n"
CanonicalizedOSSHeaders
could be an empty string when there are no x-oss-
prefixed headers, nor additional headers to sign.
AdditionalHeaders is a list of additional header names in lower case, then sorted in alphabetic order, and finally joined by semi-colon. For example, additional header names are Host
and Range
, AdditionalHeaders should be host;range
.
CanonicalizedResource represents the OSS resource targeted by the request. Construct it for a REST request as follows:
- Start with an empty string ("")
- Append UriEncoded
Resource
.Resource
is one of/BucketName/ObjectKey
,/BucketName
,/
depending on the resource you are requesting. - Append the UriEncoded query string parameters and their values. Query string parameters should be sorted by UriEncoded name in alphabetic order. If there are two fields having same name, they should be further sorted by UriEncoded value in alphabetic order. If a parameter's value is empty,
=
must be omitted.
The constructed CanonicalizedResource looks like
UriEncode(Resource) [?UriEncode(Name1)=UriEncode(Value1)&UriEncode(Name2)&UriEncode(Name3)=UriEncode(Value3]
The examples in this and following sections use the (non-working) credentials in the following table.
AccessKeyId | AccessKeySecret |
---|---|
44CF9590006BF252F707 | OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV |
This example uploads an object with key nelson
to bucket oss-example
, and object ACL is private
.
PUT /nelson HTTP/1.1
Host: oss-example.oss-cn-hangzhou.aliyuncs.com
Accept-Encoding: identity
Content-Length: 32
x-oss-object-acl: private
Accept: */*
date: Wed, 15 Feb 2017 09:37:11 GMT
content-type: text/plain
Connection: keep-alive
User-Agent: aliyun-sdk-python/2.2.3(Windows/7/AMD64;3.5.0)
content-md5: FxqG8Ca0qEJPOghSihJ8Ew==
authorization: OSS2 AccessKeyId:44CF9590006BF252F707,Signature:5Am2ewK1tL0gXX7GV6dwybZtj7efOEtc0Mo2FR6CkM8=
The constructed StringToSign is
PUT
FxqG8Ca0qEJPOghSihJ8Ew==
text/plain
Wed, 15 Feb 2017 09:37:11 GMT
x-oss-object-acl:private
%2Foss-example%2Fnelson
Below is the annotated version of above StringToSign
PUT # VERB, a.k.a. HTTP method
FxqG8Ca0qEJPOghSihJ8Ew== # Content-MD5 header value in the request
text/plain # Content-Type value in the request
Wed, 15 Feb 2017 09:37:11 GMT # Date value in the request
x-oss-object-acl:private # an 'x-oss-' prefixed header, header name should be lower cased
# no additional headers
%2Foss-example%2Fnelson # CanonicalizedResource
With StringToSign obtained above, we can calculate a signature. For example, in Python we may
import base64
import hmac
import hashlib
h = hmac.new("OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV",
"PUT\nFxqG8Ca0qEJPOghSihJ8Ew==\ntext/plain\nWed, 15 Feb 2017 09:37:11 GMT\nx-oss-object-acl:private\n\n%2Foss-example%2Fnelson", hashlib.sha256)
signature = base64.b64encode(h.digest())
The value of Signature
should be 5Am2ewK1tL0gXX7GV6dwybZtj7efOEtc0Mo2FR6CkM8=
This example downloads first 8 bytes of object nelson
in bucket oss-example
with if-modified-since
condition. Note that range
and if-modified-since
are included into signature calculation as additional headers.
GET /nelson HTTP/1.1
Host: oss-example.oss-cn-hangzhou.aliyuncs.com
Accept-Encoding: identity
User-Agent: aliyun-sdk-python/2.2.3(Windows/7/AMD64;3.5.0)
Connection: keep-alive
range: bytes=0-7
date: Thu, 16 Feb 2017 02:09:39 GMT
Accept: */*
authorization: OSS2 AccessKeyId:44CF9590006BF252F707,AdditionalHeaders:range;if-modified-since,Signature:YG9mKO3m4S0Jx9Hk6Lq64VchJg/TOTkyCX4DaeeOYxE=
if-modified-since: Thu, 16 Feb 2017 02:10:39 GMT
StringToSign should be
GET
Thu, 16 Feb 2017 02:09:39 GMT
if-modified-since:Thu, 16 Feb 2017 02:10:39 GMT
range:bytes=0-7
if-modified-since;range
%2Foss-example%2Fnelson
Below is the annotated version of above StringToSign
GET # VERB
# empty Content-MD5 header value
# empty Content-Type header value
Thu, 16 Feb 2017 02:09:39 GMT # Date
if-modified-since:Thu, 16 Feb 2017 02:10:39 GMT # CanonicalizedOSSHeaders: additional header name, value pairs
range:bytes=0-7
if-modified-since;range # AdditionalHeaders: additional header names
%2Foss-example%2Fnelson # resource
In addition to sign a request in HTTP header, authorization information can also be entirely expressed in a URL. Such URL is called a presigned URL, and it could be given to third party users, so that they can access a privileged resource via this URL. However, supplying authorization information in both HTTP header and URL is not allowed.
Example presigned URL looks like
http://oss-example.oss-cn-hangzhou.aliyuncs.com/nelson?x-oss-expires=1487152431&x-oss-signature=ps%2F%2BMLhd1WKkVi%2FQlOiliJsTaBMBk93f6UYVscDNHCQ%3D&x-oss-access-key-id=44CF9590006BF252F707&x-oss-signature-version=OSS2
A Signature Version 2 presigned URL must contain following components
-
x-oss-signature-version
indicates the version of signature algorithm, it must beOSS2
-
x-oss-expires
indicates the timeout time of the URL. It is a UNIX timestamp, which is the number of seconds that have elapsed since 00:00:00 UTC, Jan. 1, 1970. If the time when OSS server receives a request accessing this URL is later thanx-oss-expires
, a 403 HTTP status will be returned with OSS error codeAccessDenied
. -
x-oss-access-key-id
indicates AccessKeyId. -
x-oss-signature
is the calculated signature.
Additionally a presigned URL may contain x-oss-additional-headers
to indicate additional headers to be included in signature calculation. The format of x-oss-additional-headers
is a semi-colon separated list of header names.
The algorithm to calculate signature is similar to the one that constructs an authorization header. The main difference is to use Expires
, i.e. the value of x-oss-expires
to replace Date
:
StringToSign =
VERB + "\n”
+ Content-MD5 + "\n"
+ Content-Type + "\n"
+ Expires + "\n"
+ CanonicalizedOSSHeaders
+ AdditionalHeaders + "\n"
+ CanonicalizedResource
Signature = base64(hmac-sha256(AccessKeySecret, StringToSign))
Other differences are
-
CanonicalizedResource
should includex-oss-signature-version
,x-oss-access-key-id
,x-oss-expires
andx-oss-additional-headers
if any.x-oss-additional-headers
indicates additional headers to be included in signature calculation besides headers prefixed byx-oss-
. - The value of query parameters should be URL encoded. Especially the value of
x-oss-signature
should be URL encoded. - The value of
x-oss-signature-version
must beOSS2
. If this parameter is not present, OSS will use Signature Version 1 to verify the request. - The value of
x-oss-expires
is verified before signature verification.
Same AccessKeyId and AccessKeySecret in above examples are used in follow examples. A presigned URL will be constructed, with which a user can download the object before UNIX time 1487152431, i.e. Wed, 15 Feb 2017 09:53:51 GMT. StringToSign is
GET
1487152431
%2Foss-example%2Fnelson?x-oss-access-key-id=44CF9590006BF252F707&x-oss-expires=1487152431&x-oss-signature-version=OSS2
Below is the annotated version of the above StringToSign
GET # VERB, a.k.a. HTTP method
# Content-MD5 header value is empty
# Content-Type header value is empty
1487152431 # Expiration time in UNIX timestamp format
# no additional headers.
%2Foss-example%2Fnelson?x-oss-access-key-id=44CF9590006BF252F707&x-oss-expires=1487152431&x-oss-signature-version=OSS2
In Signature Version 2, query string parameters that are not meaningful to OSS are included in signature calculation too. Below is an example for this. The resulted URL is (add extra new lines for readability)
http://oss-example.oss-cn-hangzhou.aliyuncs.com/nelson?
x-oss-signature-version=OSS2&
x-oss-signature=wsARTPqvZdbdPjYpZfDZ%2FjisUaacYq7gGOdB3f1BgTE%3D&
extra-query=1&
x-oss-access-key-id=44CF9590006BF252F707&
x-oss-expires=1487211619
StringToSign is
GET
1487211619
%2Foss-example%2Fnelson?extra-query=1&x-oss-access-key-id=44CF9590006BF252F707&x-oss-expires=1487211619&x-oss-signature-version=OSS2
Note that extra-query
is not meaningful to OSS, but it is included in StringToSign.
The steps to sign a PostObject in Signature Version 2 is as follows:
- set value of form field
x-oss-signature-version
toOSS2
- set value of form field
x-oss-access-key-id
to AccessKeyId. Note in Signature Version 1 AccessKeyId is specified by form fieldOSSAccessKeyId
. - set value of form field
x-oss-signature
to the calculated signature. Note in Signature Version 1, signature is specified by form fieldSignature
. - Use AccessKeySecret to sign Base64 encoded policy with SHA256 hash algorithm. Note that Base64 encoded policy is also the value of form field
policy
.
The sign algorithm could be illustrated by following Python2 code
import hmac
import hashlib
import base64
digest = hmac.new(access_key_secret, encoded_policy, hashlib.sha256).digest()
signature = base64.b64encode(digest)
This example uploads an object with key object-from-post.txt
POST / HTTP/1.1
Host: oss-example.oss-cn-hangzhou.aliyuncs.com
Content-Length: 887
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.9.1
Connection: keep-alive
Content-Type: multipart/form-data; boundary=arbitraryboundaryvalue
--arbitraryboundaryvalue
Content-Disposition: form-data; name="policy"
eyAiZXhwaXJhdGlvbiI6ICIyMDE3LTAyLTE2VDEzOjAxOjU5LjAwMFoiLCJjb25kaXRpb25zIjogW1sic3RhcnRzLXdpdGgiLCAiJGtleSIsICIiXV19
--arbitraryboundaryvalue
Content-Disposition: form-data; name="x-oss-access-key-id"
44CF9590006BF252F707
--arbitraryboundaryvalue
Content-Disposition: form-data; name="x-oss-signature"
g5N6HBLwr0AGIH4wYHz2k7EieGCklb1I/oNp5mXc3oc=
--arbitraryboundaryvalue
Content-Disposition: form-data; name="key"
object-from-post.txt
--arbitraryboundaryvalue
Content-Disposition: form-data; name="x-oss-signature-version"
OSS2
--arbitraryboundaryvalue
Content-Disposition: form-data; name="file"; filename="object-from-post.txt"
file content for post object request
--arbitraryboundaryvalue
Content-Disposition: form-data; name="submit"
Upload to OSS
--arbitraryboundaryvalue--