AWS REST リクエストの署名についてメモ

AWS の REST API のリクエストでは署名(signature)を使って、API のセキュリティチェックを行っている。
サービス開始時に Version 1(いまは使われていない) が公開されたあと、2, 3, 4 という合計4種類が存在する。
この署名の仕様をメモ。

version 1

最も古い Version 1

?Action=CreateQueue
&QueueName=queue2
&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE
&SignatureVersion=1
&Expires=2007-01-12T12:00:00Z
&Version=2006-04-01

というような PARAM=VALUE がずらずらと並んだ GET リクエストを考える。

STEP1

パラメーターを大文字/小文字を無視して辞書順にソート。

?Action=CreateQueue
&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE
&Expires=2007-01-12T12:00:00Z
&QueueName=queue2
&SignatureVersion=1
&Version=2006-04-01

このリクエストから version1 で署名する文字列を以下のルールで生成。

クエリーストリングの先頭の ? を削除
クエリーストリングを大文字/小文字を無視して辞書順にソート
&name=value 形式の & と = を削除

※ name=value 形式の name と value は % エンコードしない

より具体的には

ActionCreateQueueAWSAccessKeyIdAKIAIOSFODNN7EXAMPLEExpires2007-01-12T12:00:00ZQueueNamequeue2SignatureVersion1Version2006-04-01

というようになっている。

この文字列を canonical string と呼ぶ。
この文字列を署名する。

STEP2

RFC2014 準拠の HMAC-SHA1 を求める。

  • HMAC のキーには AWS のシークレットアクセスキー
  • HMAC のメッセージには STEP2 で作成した文字列

STEP3

STEP2 で作成した文字列を BASE64 エンコードし、%エンコードしたものが署名。

urllib.quote_plus(hmac.new(secret_key, canonical_string, hashlib.SHA1).digest().encode('base64'))

この署名をクエリーストリングの Signature パラメーターで渡す。

この署名には大きな欠陥がある。
クエリーストリングのデリミターを利用していないため、 "foo=bar&fooble=baz""foo=barfooblebaz" は STEP2 の文字列生成で同じ文字列となり、当然、同じ署名となる。(ハッシュ値の衝突)

平文で API リクエストされると、クライアントが計算した署名を流用してリクエストパラメーターを改竄できてしまう。

ref : http://www.daemonology.net/blog/2008-12-18-AWS-signature-version-1-is-insecure.html

version 2

仕様

http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html

欠陥のある V1 を置き換えるために生まれたプロトコル。

STEP1

version2 で署名するメッセージは以下の形式をしている

HTTP_REQUEST_METHOD(GET or POST)
HOST
PATH
QUERY_STRING

より具体的には

GET\n
elasticmapreduce.amazonaws.com\n
/\n
AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Action=DescribeJobFlows&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2011-10-03T15%3A19%3A30&Version=2009-03-31

というようになっている。

この文字列を canonical string と呼ぶ

  • HTTP_REQUEST_METHOD には GET または POST がくる
  • HOST は API のリクエスト先からポートを除外したもの
  • PATH は リクエストURL のパス部。デフォルトは /

QUERY_STRING は以下のルールで生成。

  • クエリーストリングの先頭の ? を削除
  • クエリーストリングを大文字/小文字を無視して辞書順にソート
  • name=value 形式の name と value は URL エンコード

STEP2

RFC2014 準拠の HMAC-SHA256 を求める。

  • HMAC のキーには AWS のシークレットアクセスキー
  • HMAC のメッセージには STEP1 で作成した文字列

STEP3

STEP2 で作成した文字列を BASE64 エンコードし、%エンコードしたものが署名。

urllib.quote_plus(hmac.new(secret_key, canonical_string, hashlib.SHA256).digest().encode('base64'))

この署名をクエリーストリングの Signature パラメーターで渡す。

最終的には以下のような URL となる

https://elasticmapreduce.amazonaws.com?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Action=DescribeJobFlows&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2011-10-03T15%3A19%3A30&Version=2009-03-31&Signature=i91nKc4PWAt0JJIdXwz9HxZCJDdiy6cf%2FMj6vPxyYIs%3D

version 3

signature version 3 は route53 だけで利用されている。

Amazon Route 53 is the only service that requires signature version 3.
http://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html

仕様

http://docs.aws.amazon.com/Route53/latest/APIReference/Headers-Common.html#Headers

Version 1,2 とことなり、署名ををリクエストヘッダーに突っ込む。

リファレンスに仕様を見つけられなかったので SDK の実装をもとに仕様を起こす

STEP1

GMT での現在日時を取得(Sat, 22 Nov 2014 11:02:12 GMT)
リクエストヘッダーの Date または x-amz-date に突っ込む

STEP2

RFC2014 準拠の HMAC-SHA256 を求める。

  • HMAC のキーには AWS のシークレットアクセスキー
  • HMAC のメッセージには STEP1 で作成した文字列

STEP3

STEP2 で作成した文字列を BASE64 エンコードし、%エンコードしたものが署名。

urllib.quote_plus(hmac.new(secret_key, gmt_datetime, hashlib.SHA256).digest().encode('base64'))

STEP4

リクエストヘッダーに Date または X-Amz-Date と X-Amzn-Authorization に Signature に続けて STEP3 で作成した署名を入れる。

X-Amz-Date : 20141122T135429Z
X-Amzn-Authorization : AWS3-HTTPS AWSAccessKeyId=YOUR-AWS-ACCESS-KEY,Algorithm=HmacSHA256,Signature=YOUR-SIGNATURE

version 4

最後に最新にして最も複雑な Version4 のシグネチャーについて。
AWS は version 4 への移行を推奨している。

AWS currently supports three signature versions: signature version 2, signature version 3, and signature version 4. This section covers signature version 4 and signature version 2. Most services support version 4, and if a service supports version 4, we strongly recommend that you use that version.
http://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html

事実 Frankfurt や Beijin のように最近誕生したリージョンでは V2 プロトコルに対応していない。

大きな違いとしては、 V1 から V3 までは HMAC の秘密鍵に AWS の生のシークレットキーを使っているのに対し、V4 では鍵導出関数(Key Derivation Function:KDF)を使っている。

仕様

http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html

ややこしいからか、解説が異常に親切

STEP 1

リクエスト情報を含んだ canonical string を構成する。

HTTP_REQUEST_METHOD(GET or POST)
PATH
QUERY_STRING
CANONICAL_HEADER
SIGNED_HEADER
PAYLOAD

より具体的には

GET
/
Action=DescribeRegions&Version=2013-10-15
host:ec2.amazonaws.com
x-amz-date:20141122T135429Z

host;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

というようになっている。

  • HTTP_REQUEST_METHOD には GET または PUT がくる
  • PATH は リクエストURL のパス部。デフォルトは /
  • QUERY_STRING は Version 2 と同じ
  • CANONICAL_HEADER は今回はリクエストするホストとリクエスト日時
  • SIGNED_HEADER は hostx-amz-date を小文字で ; 区切りでアルファベット順に並べる。
  • PAYLOAD はリクエストボディー(GET なら空文字)の SHA256 ダイジェスト

STEP 2

version4 で署名するメッセージは以下の形式をしている

HASH_ALGORITHM
REQUEST_DATETIME
CREDENTIAL_SCOPE
CANONICAL_STRING

より具体的には

AWS4-HMAC-SHA256\n
20141122T135429Z\n
20141122/us-east-1/ec2/aws4_request\n
d8644052836bd7e18c938b2b1827a9599daed0bc5ad5f7e68cf87ddfa0153d47

というようになっている。

  • HTTP_REQUEST_METHOD にはハッシュ方式によって AWS4-HMAC-SHA256(推奨)または AWS4-HMAC-SHA1 がくる
  • REQUEST_DATETIME は GMT での現在日時
  • CREDENTIAL_SCOPE は {REQUEST_DATETIMEの年月日部分}/{aws-region}/{service-name}/aws4_request
  • CANONICAL_STRING には STEP1 で作成した文字列の SHA256 ダイジェスト

STEP3

鍵導出関数を使って署名に使うキーを生成する。

鍵は AWS の秘密鍵を初期値として HMAC-SHA256 を繰り返す。

kSecret = AWS_SECRET_KEY
kDate = HMAC("AWS4" + kSecret, Date)
kRegion = HMAC(kDate, Region)
kService = HMAC(kRegion, Service)
kSigning = HMAC(kService, "aws4_request")

あるいは

HMAC(HMAC(HMAC(HMAC("AWS4" + AWS_SECRET_KEY, Date), Region), Service), "aws4_request")

と書いたほうがわかりやすいかもしれない。

STEP 4

RFC2014 準拠の HMAC-SHA256 を求める。

  • HMAC のキーには STEP 3 で作成した文字列
  • HMAC のメッセージには STEP2 で作成した文字列

これが署名

STEP 5

リクエストヘッダーに Date または X-Amz-DateX-Amzn-Authorization に アルゴリズム、クリデンシャル、署名ヘッダー、STEP3 で作成した署名を入れる。

Host : ec2.amazonaws.com
X-Amz-Date : 20141122T135429Z
Authorization : AWS4-HMAC-SHA256 Credential=YOUR-AWS-ACCESS-KEY/20141122/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-date, Signature=YOUR-SIGNATURE

ここで Credential は {YOUR-AWS-ACCESS-KEY}/{REQUEST_DATETIMEの年月日部分}/{aws-region}/{service-name}/aws4_request で構成されている。(STEP 2 の CREDENTIAL_SCOPE に aws access key がついたもの)

署名情報はリクエストヘッダーではなくクエリーストリングや POST データに含めてもよい。

ref ; http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html

2 thoughts on “AWS REST リクエストの署名についてメモ

Leave a comment