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 は
host
とx-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-Date
と X-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
非常に助かりました。