##はじめに
AWS署名バージョン2が2019年6月24日に廃止となり、
以降 バージョン4のみサポート すると発表があったのはご存知かと。
しかしAWSドキュメントを見てもバージョン2と比べどうも情報が少ないなと感じたり、
手順も見づらいなと思いながらも、試しに実装をしてみました。
今後対応が必要になる方のお役に立てればと、作成手順をここに残します。
※本記事はAuthorizationヘッダーでの認証方法です。
その他のやり方として、クエリストリングでの方法もあります。
※今回はPUTを例として記載しています。
※署名やハッシュ文字列はテスト用で出力されたもののため、実際の利用はできません。
##手順
###タスク 1:署名バージョン 4 の正規リクエストを作成する
https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-canonical-request.html
まずは、例となっている正規リクエストの擬似コードを御覧ください。
CanonicalRequest =
HTTPRequestMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HexEncode(Hash(RequestPayload))
* HTTPRequestMethod
→ メソッド。GETやPUT、POSTでOK。
* CanonicalURI
→ URI。http://example.amazonaws.com 等の接続先を指定。
* CanonicalQueryString
→ クエリストリング。あれば通常の表記、なければ空文字列でOK。
* CanonicalHeaders
→ ヘッダー情報。"Host"ヘッダーが必須。
条件 1.キーは小文字
2.キーと値はコロンで区切られている
3.キーはソート(ASC)されている
4.ヘッダー間は改行で区切られている
■実装例
@headers['Host'] = "example.amazonaws.com"
@headers['X-Amz-Content-Sha256'] = "xyz" #16進数ダイジェスト(xyzは仮
def canonical_headers
list = []
@headers.each_pair do |k,v|
k = k.downcase
list << [k,v]
end
list.sort.map do |k,v|
"#{k}:#{v}"
end.join("\n")
end
# 結果
"host:example.amazonaws.com
x-amz-content-sha256:xyz"
* SignedHeaders
→ ヘッダーリスト。キーのみ。
条件 1.キーは小文字
2.キーはソート(ASC)されている
3.キー感はセミコロンで区切られている
■実装例
@headers['Host'] = "example.amazonaws.com"
@headers['X-Amz-Content-Sha256'] = "xyz" #16進数ダイジェスト
def signed_headers
@headers.keys.inject([]) do |list, k|
k = k.downcase
list << k
end.sort.join(';')
end
# 結果
"host;x-amz-content-sha256"
* HexEncode(Hash(RequestPayload))
→ PUT対象の16進数ダイジェスト(SHA256)。
※ ファイルのPUTの場合はreadが必要です
■実装例
def hexdigest(value)
digest = OpenSSL::Digest::SHA256.new
if value.respond_to?(:read)
digest.update(value.read)
else
digest.update(value)
end
end
これらの各手順の全てを改行にて結合すると
■実装例
def canonical_request
[
"PUT",
"http://example.amazonaws.com",
"",
canonical_headers + "\n",
signed_headers,
hexdigest("xyz"),
].join("\n")
end
■結果
PUT
http://example.amazonaws.com
host:example.amazonaws.com
x-amz-content-sha256:xyz
host;x-amz-content-sha256
3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282
となります。
仕上げに、これらの正規リクエストを16進数ダイジェストに変換します。
# 実行
hexdigest(canonical_request)
# 結果
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
###タスク 2:署名バージョン 4 の署名文字列を作成する
https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-string-to-sign.html
まずは、例となっている署名文字列の構造を御覧ください。
StringToSign =
Algorithm + \n +
RequestDateTime + \n +
CredentialScope + \n +
HashedCanonicalRequest
* Algorithm
→ 使用するハッシュアルゴリズム。SHA256の場合は"AWS4-HMAC-SHA256"。
* RequestDateTime
→ 日時。YYYYMMDD'T'HHMMSS'Z'形式である必要がある。
* CredentialScope
→ 認証情報スコープ。日付、対象とするリージョン、リクエストしているサービス、
および小文字の終了文字列 ("aws4_request") を含む文字列を作る。
20190301/us-east-1/s3/aws4_request
* HashedCanonicalRequest
→ 正規リクエストのハッシュ。タスク1の仕上げで作成したハッシュ(下記結果)を入れる。
# 実行
hexdigest(canonical_request)
# 結果
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
これらの各文字列の全てを改行にて結合すると
■実装例
def string_to_sign
[
"AWS4-HMAC-SHA256",
"20190301T123600Z",
"20190301/us-east-1/s3/aws4_request",
hexdigest(canonical_request),
].join("\n")
end
■結果
AWS4-HMAC-SHA256
20190301T123600Z
20190301/us-east-1/s3/aws4_request
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
となります。
###タスク 3:AWS 署名バージョン 4 の署名を計算する
https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-calculate-signature.html
まずは、例となっている署名キー取得の疑似コードを御覧ください。
※各パラメータはバイナリ形式である必要があります。
kSecret = your secret access key
kDate = HMAC("AWS4" + kSecret, Date)
kRegion = HMAC(kDate, Region)
kService = HMAC(kRegion, Service)
kSigning = HMAC(kService, "aws4_request")
* HMAC("AWS4" + kSecret, Date)
→ kSecret = シークレットキー、Date = 日付(YYYYMMDD形式)。
* HMAC(kDate, Region)
→ Region = 対象とするリージョン
* HMAC(kRegion, Service)
→ Service = リクエストしているサービス
* HMAC(kService, "aws4_request")
→ aws4_request = 固定文字列
※バイナリ形式は下記のような手段で変換を行う
def hmac(key, value)
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
end
仕上げに、署名(signature)の計算を行います。 ※こちらはバイナリ値16進数表現
■実装例
def signature
kSecret = your secret access key
kDate = hmac("AWS4" + kSecret, Date)
kRegion = hmac(kDate, Region)
kService = hmac(kRegion, Service)
kSigning = HMAC(kService, "aws4_request")
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), kSigning, string_to_sign)
end
■結果
5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
となります。
###タスク 4:HTTP リクエストに署名を追加する
https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-add-signature-to-request.html
まずは、例となっているAuthorizationヘッダーの疑似コードを御覧ください。
Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature
* algorithm
→ 使用するハッシュアルゴリズム。SHA256の場合は"AWS4-HMAC-SHA256"。
* access key ID/credential scope
→ access key ID = アクセスキー。
→ credential scope = 認証情報スコープ。日付、対象とするリージョン、リクエストしているサービス、
および小文字の終了文字列 ("aws4_request") を含む文字列を作る。
* SignedHeaders
→ ヘッダーリスト。キーのみ。 ※タスク1にて作成(signed_headerメソッド)
* signature
→ 署名。 ※タスク3にて作成(signatureメソッド)
# 最終的なヘッダー例
Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
##ソースコード
ソースの全容
@heds = {}
@heds['Host'] = "example.amazonaws.com"
@heds['X-Amz-Content-Sha256'] = @bodydigest
@bodydigest = hexdigest(File.open("test.text").read) #16進数ダイジェスト
@dt = "20190301T123600Z"
@region = "s-east-1"
@service = "s3"
@scope = [@dt[0,8], @region, @service, 'aws4_request'].join("/")
@secretkey = "aaaaaa"
@accesskey = "bbbbbb"
# Authorizationヘッダーの生成
def _get_authorization(access_key_id, secret_key)
[
"AWS4-HMAC-SHA256 Credential=#{@accesskey}/#{@scope}",
"SignedHeaders=#{signed_headers}",
"Signature=#{signature}",
].join(',')
end
#正規リクエストの取得
def canonical_request
[
"PUT",
"http://example.amazonaws.com",
"",
canonical_headers + "\n",
signed_headers,
@bodydigest,
].join("\n")
end
# 署名文字列の取得
def string_to_sign
[
"AWS4-HMAC-SHA256",
@dt,
@scope,
hexdigest(canonical_request),
].join("\n")
end
# 署名の生成
def signature
kSecret = @secretkey
kDate = hmac("AWS4" + kSecret, @dt[0.8])
kRegion = hmac(kDate, @region)
kService = hmac(kRegion, @service)
kSigning = hmac(kService, "aws4_request")
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), kSigning, string_to_sign)
end
# リクエストヘッダー
def canonical_headers
list = []
@heds.each_pair do |k,v|
k = k.downcase
list << [k,v]
end
list.sort.map do |k,v|
"#{k}:#{v}"
end.join("\n")
end
# ヘッダーキーリスト
def signed_headers
@heds.keys.inject([]) do |list, k|
k = k.downcase
list << k
end.sort.join(';')
end
# 16進数ダイジェスト(SHA256)
def hexdigest(value)
digest = OpenSSL::Digest::SHA256.new
if value.respond_to?(:read)
digest.update(value.read)
else
digest.update(value)
end
end
# hmac変換
def hmac(key, value)
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
end
これらがお困りの方のお役に立てたらこれ幸いです。