本記事は、Azure StorageでのRest APIの署名作成についての調査結果をまとめたものになります。
前提
- ストレージアカウント等は、本記事中では以下の識別子を使用しています。
項目 | 本記事での識別子 |
---|---|
ストレージアカウント | mystorageaccount |
コンテナ名 | mycontainer |
PUT/GET/DELETEするBlob名 | sample.txt |
- オペレーションごとの、リクエストヘッダおよび
StringToSign
に必要なヘッダは以下の通りです。
(〇:必須 -:不要)
Request Header | Put Blob | Get Blob | Detele Blob | List Blobs |
---|---|---|---|---|
Content-Length | 〇 | - | - | - |
x-ms-date (or Date) | 〇 | 〇 | 〇 | 〇 |
x-ms-version | 〇 | 〇 | 〇 | 〇 |
x-ms-blob-type | 〇 | - | - | - |
- リクエストヘッダには
x-ms-date
とDate
のうち少なくとも一方が存在する必要があります。リクエストヘッダにいずれか一方のみが存在する場合、StringToSign
にはリクエストヘッダに存在する方のみを記述し、存在しない方は記述しません。リクエストヘッダに両方が同時に存在する場合、StringToSign
にはx-ms-date
のみ記述します。
リクエストヘッダ | StringToSignへの記述 |
---|---|
x-ms-dateのみ存在 | x-ms-dateのみ記述する |
Dateのみ存在 | Dateのみ記述する |
上2つが同時に存在 | x-ms-dateのみ記述する |
- 本記事に出てくる「アクセスキー」は、Azureポータルの以下画面の「キー」で確認できます。
([ホーム] > [ストレージアカウント]でストレージアカウントを選択 > [アクセスキー])
-
アクセスキーは、Base64エンコードされた文字列です。本記事では、例示のために
VGhpcyBpcyBzYW1wbGUgb2YgQXp1cmUgU3RvcmFnZSBBY2Nlc3MgS2V5IHN0cmluZyBCYXNlNjQgRW5jb2RlZA==
というキーを使用していますが、これは実在しないキーです。くれぐれも本物のアクセスキーをソースコードにハードコーディングしないようにご注意下さい。 -
本記事のサンプルコードはC#で記述していますが、C#特有の文法は使用していないため、C#未経験者でも内容が直観的に理解できるコードになっています。念のため説明しておきますと、
string s = "abc";
はString型(文字列型)の変数s
を宣言し、その値を文字列"abc"
で初期化するという意味です。また、byte[] stringToSign_bytes;
は、バイト配列型の変数stringToSign_bytes
を宣言するという意味です。
署名のつくりかた
おおまかな流れとしては、以下となります。
-
StringToSign
を作成する。 - アクセスキー(
AccessKey
)をキーとして、StringToSign
のHMAC-SHA256ハッシュ値を算出する。 - 2で算出したHMAC-SHA256ハッシュ値をBase64エンコードした文字列を、リクエストヘッダの
Authoraization
に設定する。
AWS S3のSignature V4の手順と比べると、Azure Storaogeの手順はかなりシンプルで助かります。
以下では、Put/Get/List/Deleteの各オペレーションでの署名の作成手順について解説します。
Put Blob
StringToSign
を作成します。
-
StringToSign
の末尾(CanonicalResource
の末尾)に改行文字はつけません。 - HTTPヘッダと
StringToSign
において、リクエストヘッダContent-Length
,x-ms-blob-type
、x-ms-date
またはDate
、x-ms-version
が必須です。 -
CanonicalHeaders
のキー名は小文字です。 -
CanonicalHeaders
の並び順は、キー名小文字での辞書順(昇順)です。
string StringToSign =
// GeneralHeaders
"PUT\n" + // Verb
"\n" + // Content-Encoding
"\n" + // Content-Language
"4\n" + // Content-Length (Required)
"\n" + // Content-MD5
"\n" + // Content-Type
"\n" + // Date
"\n" + // If-Modified-Since
"\n" + // If-Match
"\n" + // If-Non-Match
"\n" + // If-Unmodified-Since
"\n" + // Range
// CanonicalHeaders
"x-ms-blob-type:BlockBlob\n" +
"x-ms-date:Sun, 08 Mar 2020 03:39:02 GMT\n" +
"x-ms-version:2017-07-29\n" +
// CanonicalResource
"/mystorageaccount/mycontainer/sample.txt";
作成したStringToSign
と、アクセスキーをインプットとしてして、HMAC-SHA256のハッシュ値を算出します。
算出したハッシュ値(バイト配列)を、Base64エンコードして文字列にします。
(以下のコード例では、例示のためアクセスキーをソースコードにハードコーディングしていますが、これは実在しないキーです。くれぐれも本物のアクセスキーをソースコードに記述しないよう、ご注意下さい)
string AccessKey = "VGhpcyBpcyBzYW1wbGUgb2YgQXp1cmUgU3RvcmFnZSBBY2Nlc3MgS2V5IHN0cmluZyBCYXNlNjQgRW5jb2RlZA==";
byte[] accessKey_bytes = Base64.DecodeFromBase64String(AccessKey);
byte[] stringToSign_bytes = UTF8.GetBytes(StringToSign);
byte[] signature_bytes = HMAC_SHA256.ComputeHash(stringToSign_bytes, accessKey_bytes);
string signature_base64 = Base64.EncodeToBase64String(signature_bytes);
string Signature = signature_base64;
// ex) Signature is "QmFzZTY044OH44Kz44O844OJ44GX44G+44GX44GfPw=="
完成したSharedKey
を、HTTP RequestのAuthorization
ヘッダにセットします。
string SharedKey = "SharedKey mystorageaccount:" + Signature;
// ex) SharedKey is "SharedKey mystorageaccount:QmFzZTY044OH44Kz44O844OJ44GX44G+44GX44GfPw=="
httpRequestHeaders.Authorization = new AuthorizationHeaderValue(SharedKey);
最終的に、Authorizationヘッダは以下のようになります。
Authorization: SharedKey mystorageaccount:QmFzZTY044OH44Kz44O844OJ44GX44G+44GX44GfPw==
Get Blob
- HTTPヘッダと
StringToSign
において、リクエストヘッダx-ms-date
(またはDate
)、x-ms-version
が必須です。
string StringToSign =
// GeneralHeaders
"GET\n" + // Verb
"\n" + // Content-Encoding
"\n" + // Content-Language
"\n" + // Content-Length
"\n" + // Content-MD5
"\n" + // Content-Type
"\n" + // Date
"\n" + // If-Modified-Since
"\n" + // If-Match
"\n" + // If-Non-Match
"\n" + // If-Unmodified-Since
"\n" + // Range
// CanonicalHeaders
"x-ms-date:Sun, 08 Mar 2020 03:39:02 GMT\n" +
"x-ms-version:2017-07-29\n" +
// CanonicalResource
"/mystorageaccount/mycontainer/sample.txt";
以降の手順はPut Blobと同じです。
List Blobs
-
CanoncalResource
末尾にクエリー文字列がつきます。 -
StringToSign
の末尾(restype:container
の末尾)に改行文字はつきません。 - HTTPヘッダと
StringToSign
において、リクエストヘッダx-ms-date
またはDate
、x-ms-version
が必須です。 -
CanonicalResource
に、クエリー文字列(comp=list&restype=container
)をkey:value
の形式でリスト化した文字列が必須です。 -
CanonicalResource
のクエリーの名前(comp
、restype
)は小文字です。 -
CanonicalResource
のクエリーの並び順は、キー名小文字での辞書順(昇順)です。
string StringToSign =
// GeneralHeaders
"GET\n" + // Verb
"\n" + // Content-Encoding
"\n" + // Content-Language
"\n" + // Content-Length
"\n" + // Content-MD5
"\n" + // Content-Type
"\n" + // Date
"\n" + // If-Modified-Since
"\n" + // If-Match
"\n" + // If-Non-Match
"\n" + // If-Unmodified-Since
"\n" + // Range
// CanonicalHeaders
"x-ms-date:Sun, 08 Mar 2020 03:39:02 GMT\n" +
"x-ms-version:2017-07-29\n" +
// CanonicalResource
"/mystorageaccount/mycontainer\n" +
"comp:list\n" +
"restype:container";
以降の手順はPut Blobと同様です。
Delete Blob
- HTTPヘッダと
StringToSign
において、リクエストヘッダx-ms-date
(またはDate
)、x-ms-version
が必須です。
string StringToSign =
"DELETE\n" + // Verb
// GeneralHeaders
"\n" + // Content-Encoding
"\n" + // Content-Language
"\n" + // Content-Length
"\n" + // Content-MD5
"\n" + // Content-Type
"\n" + // Date
"\n" + // If-Modified-Since
"\n" + // If-Match
"\n" + // If-Non-Match
"\n" + // If-Unmodified-Since
"\n" + // Range
// CanonicalHeaders
"x-ms-date:Sun, 08 Mar 2020 03:39:02 GMT\n" +
"x-ms-version:2017-07-29\n" +
// CanonicalResource
"/mystorageaccount/mycontainer/sample.txt";
以降の手順はPut Blobと同じです。
【参考】実際のパケットキャプチャ
参考情報として、各操作でのパケットキャプチャを乗せておきます。
Put Blob
PUT /mycontainer/sample.txt HTTP/1.1
Authorization: SharedKey mystorageaccount:QmFzZTY044OH44Kz44O844OJ44GX44G+44GX44GfPw==
x-ms-date: Sun, 08 Mar 2020 04:46:28 GMT
x-ms-version: 2017-07-29
x-ms-blob-type: BlockBlob
Host: mystorageaccount.blob.core.windows.net
Content-Length: 4
Expect: 100-continue
Connection: Keep-Alive
hoge
HTTPレスポンスのContent-MD5
には、リクエストのメッセージボディ(hoge
)のMD5ハッシュ値をBase64エンコードした文字列(6nA+eqHv2gBk6qUH2eirfg==
)がセットされます。
HTTP/1.1 201 Created
Content-Length: 0
Content-MD5: 6nA+eqHv2gBk6qUH2eirfg==
Last-Modified: Sun, 08 Mar 2020 04:46:27 GMT
ETag: "0x8D7C31BA9E7D293"
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: d738ae8f-101e-0058-4404-f51de0000000
x-ms-version: 2017-07-29
x-ms-request-server-encrypted: true
Date: Sun, 08 Mar 2020 04:46:26 GMT
Get Blob
GET /mycontainer/sample.txt HTTP/1.1
x-ms-date: Sun, 08 Mar 2020 04:46:28 GMT
x-ms-version: 2017-07-29
Authorization: SharedKey mystorageaccount:QmFzZTY044OH44Kz44O844OJ44GX44G+44GX44GfPw==
Host: mystorageaccount.blob.core.windows.net
HTTPレスポンスのContent-MD5
には、レスポンスのメッセージボディ(hoge
)のMD5ハッシュ値をBase 64エンコードした文字列(6nA+eqHv2gBk6qUH2eirfg==
)がセットされます。
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: application/octet-stream
Content-MD5: 6nA+eqHv2gBk6qUH2eirfg==
Last-Modified: Sun, 08 Mar 2020 04:46:27 GMT
Accept-Ranges: bytes
ETag: "0x8D7C31BA9E7D293"
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: d738aeb5-101e-0058-6304-f51de0000000
x-ms-version: 2017-07-29
x-ms-lease-status: unlocked
x-ms-lease-state: available
x-ms-blob-type: BlockBlob
x-ms-server-encrypted: true
Date: Sun, 08 Mar 2020 04:46:27 GMT
hoge
List Blobs
GET /mycontainer?restype=container&comp=list HTTP/1.1
x-ms-date: Sun, 08 Mar 2020 04:46:28 GMT
x-ms-version: 2017-07-29
Authorization: SharedKey mystorageaccount:QmFzZTY044OH44Kz44O844OJ44GX44G+44GX44GfPw==
Host: mystorageaccount.blob.core.windows.net
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: d738aee2-101e-0058-0b04-f51de0000000
x-ms-version: 2017-07-29
Date: Sun, 08 Mar 2020 04:46:27 GMT
<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults ServiceEndpoint="http://mystorageaccount.blob.core.windows.net/" ContainerName="mycontainer">
<Blobs>
<Blob>
<Name>sample.txt</Name>
<Properties>
<Last-Modified>Sun, 08 Mar 2020 04:46:27 GMT</Last-Modified>
<Etag>0x8D7C31BA9E7D293</Etag>
<Content-Length>4</Content-Length>
<Content-Type>application/octet-stream</Content-Type>
<Content-Encoding />
<Content-Language />
<Content-MD5>6nA+eqHv2gBk6qUH2eirfg==</Content-MD5>
<Cache-Control />
<Content-Disposition />
<BlobType>BlockBlob</BlobType>
<AccessTier>Hot</AccessTier>
<AccessTierInferred>true</AccessTierInferred>
<LeaseStatus>unlocked</LeaseStatus>
<LeaseState>available</LeaseState>
<ServerEncrypted>true</ServerEncrypted>
</Properties>
</Blob>
</Blobs>
<NextMarker />
</EnumerationResults>
Delete Blob
DELETE /mycontainer/sample.txt HTTP/1.1
x-ms-date: Sun, 08 Mar 2020 04:46:28 GMT
x-ms-version: 2017-07-29
Authorization: SharedKey mystorageaccount:QmFzZTY044OH44Kz44O844OJ44GX44G+44GX44GfPw==
Host: mystorageaccount.blob.core.windows.net
Content-Length: 0
HTTP/1.1 202 Accepted
Content-Length: 0
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: d738af03-101e-0058-2904-f51de0000000
x-ms-version: 2017-07-29
x-ms-delete-type-permanent: true
Date: Sun, 08 Mar 2020 04:46:27 GMT
参考
Blob service REST API (公式)
Microsoft Azure~REST API 操作やってみた~ … PHPでの実装例です。とても分かりやすくて、何度も参考にさせていただきました。