Amazon アソシエイト Web サービス改め Product Advertising API の電子署名について調べてみました
Amazon アソシエイト Web サービスが Product Advertising API に名称変更されたのにともない、リクエストごとに電子署名を含めなければならなくなるようです。これは 2009 年 5 月 11 日から同 8 月 15 日まで段階的に導入され、その後、認証されないリクエストについては処理されなくなるとのことです。
私はこの件について最初にメールで知ったのですが、Amazon アソシエイト・プログラム(アフィリエイト) 公式ブログ: Amazon アソシエイト Web サービスの名称変更および署名認証についてのお知らせにも同様の記事が掲載されています。詳しくはそちらを参照してください。
なんだか非常にややこしそうな話で、すでに半ば心が折れそうな状態ですが、ちょっとがんばって調べてみました。まだ書きかけの部分もありますが、あとで調べなおしたことがあれば随時追記していきます。
なお以下、REST にしぼって話を進めます。SOAP については、Authenticating SOAP Requests (Product Advertising API) を参照してください。
認証用のパラメータ
Authentication Parameters (Product Advertising API) より:
Signature
Timestamp
- Signature パラメータを含める場合は必須。それ以外の場合はオプション。
- デフォルト値なし。
- dateTime オブジェクトでなければならない。
- 日付+時間、分、秒(YYYY-MM-DDThh:mm:ssZ)
- 参考: http://www.w3.org/TR/xmlschema-2/#dateTime
- ISO 8601 で定義されているフォーマットの固定長サブセット。
- Universal Time (GMT) で表される。
- YYYY-MM-DDThh:mm:ssZ
The mapping so defined is one-to-one, except that '+00:00', '-00:00', and 'Z' all represent the same zero-length duration timezone, UTC; 'Z' is its canonical representation.
http://www.w3.org/TR/xmlschema-2/#dateTime
認証までの手順
Basic Authentication Process (Product Advertising API) より:
Amazon Web サービス(AWS)のアカウントを取得済みで、Access Key ID および Secret Access Key もすでに知っているものという前提で。
あなた(私)側
- AWS へのリクエストを作成
- Secret Access Key を使って HMAC-SHA 署名を作成
- HMAC-SHA 参考リンク: http://www.faqs.org/rfcs/rfc2104.html
- リクエストと署名を AWS に送信
アクセスキー ID およびシークレットアクセスキーの確認方法
REST リクエストの例
Example REST Requests (Product Advertising API) より:
- タイムスタンプの追加
- 上の例の末尾に 「&Timestamp=〜」という形で追加。
- ここでは、「2009-01-01T12:00:00Z」というタイムスタンプを使用。
- カンマ、コロンを URL エンコード
- カンマは、ResponseGroup を複数指定する場合などに使われる。
- コロンは、Timestamp で使われる。
- Example REST Requests (Product Advertising API) では、「colon(;)」と括弧の中がセミコロンになっている。
- パラメータ/値のペアに分割し、アンパサンド(&)を削除
- 上のペアをバイト値によってソート
- アルファベット順ではない。小文字のパラメータは大文字のパラメータより後になる。
- 「初めてのPython 第2版」の p.554 によると、sort メソッドでは「通常、大文字は小文字よりも前に置かれます」とあるので、普通に sort() で大丈夫かも。
- sort( )について少し詳しく - ひきメモによると、文字列は文字コード順とある。
- アンパサンドで再結合
- 再結合した文字列の上に「GET」「webservices.amazon.com」「/onca/xml」の 3 行を追加:
- 署名用の文字列の完成
- Secret Access Key と上の文字列を使って、SHA256*2 ハッシュアルゴリズムで RFC 2104 互換の HMAC*3 を算出。
- プラス(+)とイコール(=)を URL エンコード
- URL エンコードされた署名をリクエストに追加(「&Signature=〜」)
- Example REST Requests (Product Advertising API) の例だと、9 と 10 で署名の値が違うような気がする。9 では「Nace%2BU3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg%3D」、10 では「pwqYQRc3RepIrf7m%2BVMRy%2FjFXx%2FZBSPsaSFFexIUoSI%3D」になっている。[を] アマゾンAPIを使うのに2009年8月15日から認証が必要になるらしいによると、サンプルコードが一度直されたみたいなので、その名残りかもしれません。
Python で電子署名を作るには
Python でソートするには
sort か sorted を使う
参考:
- 映像奮闘記: Pythonのソートについて: sort()とsorted()
- sort と sorted の違いとか。
- sort( )について少し詳しく - ひきメモ
- 標準では、文字列は文字コード順でソートされる。
Python で URL エンコードするには
urllib.urlencode か urllib.quote(または urllib.quote_plus)?
カンマとコロンだけでいいのなら、正規表現の置換でもいいのだろうか?
Python で SHA256 を求めるには
PythonでMD5・SHA1を求める - Fioの素敵な日々によると、Python で SHA256 を求めるには hashlib を使うといいらしいです。
よく調べてみると、鍵付きハッシュでは hmac モジュールを使うらしいことがわかりました。そして、new() の digestmod に hashlib.sha256 コンストラクタを指定すると。
参考: 10.2 hmac -- メッセージ認証のための鍵付きハッシュ化、10.1 hashlib -- セキュアハッシュおよびメッセージダイジェスト
Python で Base64 エンコードするには
base64 モジュールを使って、Base64 にエンコードできるようです。7.11 base64 -- RFC 3548: Base16, Base32, Base64 テータの符号化を参照。
ほかに binascii モジュールでも Base64 エンコードは可能なようです。ただし、最後に改行文字が含まれるため、今回の場合はそれを取り除く必要があると思われます(rstrip()?)。
参考: PythonでBase64・Hexを求める - Fioの素敵な日々、7.13 binascii -- バイナリデータと ASCIIデータとの間での変換
Google App Engine で利用できるライブラリか否か
Google App Engine Python Library Support - Google App Engine - Google Code を見る。
base64 モジュールはサポートされていない模様。要確認。無理なら、binascii を使用する方向で考える。
【2009-05-22 0:40 追記】実際に試してみましたが、Google App Engine 上でも base64 モジュールは使えるようです。【追記終わり】
実際にやってみた
Example REST Requests を Python で
Example REST Requests (Product Advertising API) を、実際に Python でやってみました。Access Key ID、Secret Access Key およびタイムスタンプは、Example REST Requests の中で使われているもの(ダミー)をそのまま使っています。
[を] アマゾンAPIを使うのに2009年8月15日から認証が必要になるらしいとAmazon Product Advertising APIの認証の件 - zorioの日記を参考にさせていただきました。
# -*- coding: utf-8 -*- """ AWS Product Advertising API の電子署名を生成するテスト """ # モジュールのインポート import urllib # URL エンコード import hashlib, hmac # HMAC-SHA256 の算出 import base64 # Base64 エンコード(base64 の場合) #~ import binascii # Base64 エンコード(binascii の場合) # Secret Access Key secret_access_key = "1234567890" # ダミー # リクエスト(リスト) reqs = [ "Service=AWSECommerceService", "AWSAccessKeyId=00000000000000000000", "Operation=ItemLookup", "ItemId=0679722769", "ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews", "Version=2009-01-06", "Timestamp=2009-01-01T12%3A00%3A00Z" ] # sort でソート reqs.sort() # & で再結合 req_rejoined = "&".join(reqs) # 追加の 3 行と結合して、署名用の文字列の完成 message = """GET webservices.amazon.com /onca/xml """ + req_rejoined # Secret Access Key を使って HMAC-SHA256 を算出 hmac_digest = hmac.new(secret_access_key, message, hashlib.sha256).digest() # Base64 エンコード base64_encoded = base64.b64encode(hmac_digest) # base64 の場合 #~ base64_encoded = binascii.b2a_base64(hmac_digest).rstrip() # binascii の場合。改行文字を取り除く # URL エンコード result = urllib.quote(base64_encoded) # 結果 print result #=> Nace%2BU3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg%3D
ためしに自分の Access Key ID と Secret Access Key を使ってリクエストしてみたところ、エラーが出ることなく商品情報を取得することができました。
ジェイムズ・ジョイスの「ユリシーズ」だったのですね!
リクエストが辞書の場合
上の Example REST Requests の例を元にしたものは、URL エンコード済みの「パラメータ=値」のリストを対象にしていました。今度は URL エンコード前の状態からはじめて、リクエストの部分をリストではなく辞書(ディクショナリ)にしてやってみました。コメントアウトしていますが、タイムスタンプの生成部分も追加しています。
無題メモランダム: amazon Product Advertising APIの署名認証をPythonでやってみるを参考にさせていただきました。
# -*- coding: utf-8 -*- """ AWS Product Advertising API の電子署名を生成するテスト(その 2) """ # モジュールのインポート #~ from datetime import datetime # タイムスタンプの生成 import urllib # URL エンコード import hashlib, hmac # HMAC-SHA256 の算出 import base64 # Base64 エンコード(base64 モジュール) #~ import binascii # Base64 エンコード(binascii モジュール) # 初期設定 aws_domain = "webservices.amazon.com" # 日本なら "webservices.amazon.co.jp" access_key_id = "00000000000000000000" # ダミー secret_access_key = "1234567890" # ダミー # タイムスタンプの生成 timestamp = "2009-01-01T12:00:00Z" # テスト用 #~ utc_time = datetime.utcnow() # UTC 現在日時 #~ timestamp = utc_time.strftime("%Y-%m-%dT%H:%M:%SZ") # 整形 # リクエスト(辞書) reqs = { "Service": "AWSECommerceService", "AWSAccessKeyId": access_key_id, "Operation": "ItemLookup", "ItemId": "0679722769", "ResponseGroup": "ItemAttributes,Offers,Images,Reviews", "Version": "2009-01-06", "Timestamp": timestamp } # sorted でソート reqs_sorted = sorted(reqs.items()) # items() でキーと値をペアにしたタプルのリストを対象に #~ # URL エンコード #~ reqs_urlencoded = urllib.urlencode(reqs_sorted) # パラメータと値のペアが & で結合された文字列を返す # URL エンコード(2009-05-28 修正) req_list = [] # リストの初期化 for req in reqs_sorted: # key=value のペアに整形 pair = "%s=%s" % (req[0], urllib.quote(req[1])) # quote() で URL エンコード # リストに追加 req_list.append(pair) # & で結合された文字列を生成 reqs_urlencoded = "&".join(req_list) # 追加の 3 行を改行(\n)を使って結合、署名用の文字列の完成 message = "\n".join(["GET", aws_domain, "/onca/xml", reqs_urlencoded]) # Secret Access Key を使って HMAC-SHA256 を算出 hmac_digest = hmac.new(secret_access_key, message, hashlib.sha256).digest() # Base64 エンコード base64_encoded = base64.b64encode(hmac_digest) # base64 の場合 #~ base64_encoded = binascii.b2a_base64(hmac_digest).rstrip() # binascii の場合。改行文字を取り除く # URL エンコード(2 回目) signature = urllib.quote(base64_encoded) # quote_plus()? # 正規化されたリクエスト formatted_request = "http://" + aws_domain + "/onca/xml?" + reqs_urlencoded + "&Signature=" + signature # 結果表示 print signature #=> Nace%2BU3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg%3D print formatted_request #=> http://webservices.amazon.com/onca/xml?AWSAccessKeyId=00000000000000000000&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-06&Signature=Nace%2BU3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg%3D
感想など
あとで書く
関連リンク
Python
- 無題メモランダム: amazon Product Advertising APIの署名認証をPythonでやってみる
- laclefblog - AmazonのProduct Advertising API
- リクエストが辞書の場合は、sorted でソートして、urllib.urlencode で URL エンコード。
JavaScript
- hail2u.net - Weblog - Amazon Product Advertising APIの認証をJavaScriptで
- Secret Access Key を隠す方法が問題とのこと。
その他
- 2009-05-09 - Crayons
- 「文字数がmod 4で0になるまで=で埋め」る必要がある。
- AWSからProduct Advertising APIへ
- 言語別リンク集。
追記
2009-05-10 22:34 追記
- 404 Blog Not Found:perl - URI::Amazon::APA released! にて、Perl 用のモジュールが公開されました。Python 版も、そのうち誰かが作ってくれるんじゃないかという淡い期待。
2009-05-10 22:49 追記
- 「認証までの手順」を更新。
2009-05-12 0:08 追記
2009-05-12 2:15 追記
2009-05-13 0:06 追記
- 「実際にやってみた」を追加。
2009-05-13 0:54 追記
- 「関連リンク」を更新。
- タイプミスの修正。
2009-05-14 1:08 追記
- 「実際にやってみた」を更新。
- 「関連リンク」を更新。
2009-05-22 0:41 追記
- Amazon アソシエイト・プログラム(アフィリエイト) 公式ブログ: Product Advertising API の署名認証についてで、よくある質問と開発者ガイドの日本語参考訳が紹介されていましたので、関連リンクに追加しました。
2009-05-28 0:55 追記
- 「実際にやってみた」を更新。
- urllib.urlencode を使って URL エンコードすると半角スペースが + に置換されるため、Operation=ItemSearch で複数のキーワードを指定して検索するような場合に正常にデータが取得できませんでした。このため、「リクエストが辞書の場合」のコードを修正しました。
- 無題メモランダム: Amazon Product Advertising APIの署名認証をPythonでやってみるでも同じ問題について追記されています。コードを修正する際にも参考にさせていただきました。
- 関連記事: あまびき更新情報(2009-05-28) - にーやんのブログ 2
*1:参考: GMT と UTC の違いは何 ?
*2:参考: SHA - Wikipedia、「SHA-256/384/512」とは:ITpro
*3:Keyed-Hashing for Message Authentication code の略。参考: HMAC - Wikipedia、HMAC: Keyed-Hashing for Message Authentication