AmazonアソシエイトWebサービスの名称変更に伴う署名認証(Perl)

 Amazonアソシエイトで、Webサービスを利用している方々のホットな話題です。

 いや〜、参りました。

 突如メールが届いて、簡単に言えば「3ヶ月の猶予期間をあげるから、その間にAWSを使っているアプリの仕様変更をして下さい」とのことです。
 仕様変更の内容はというと、リクエスト日時をTimestampパラメータに含めることと、シークレット・アクセス・キーを使って「RFC2104 HMAC-SHA256」で署名ハッシュを作ってリクエストに追加するというもの。

 いや〜、参りました(笑)。
 何の事かさっぱりだったのですが、ググりまくって日本語やら英語やら読み漁って、ようやく移行完了。

 まず、さくらインターネットを使っていて、PerlのDigest::SHAが使えないのでCPANをユーザレベルでインストール。こちらを参考にしました。
 CPANでDigest::SHAをインストールしたものの、どういじってもPerlスクリプトから見られないので、仕方なくスクリプト内に、

use lib qw(〜場所〜);

を書くことに。これにより、

use Digest::SHA qw(hmac_sha256_base64);

を書けます。

 で、後は各パラメータをソートした後、「hmac_sha256_base64」を使用してSignatureパラメータを作ってやるわけですが、この辺を参考にすればOK。
 ただし、日本語のキーワードで検索する場合は、

ecs.amazonaws.jp
/onca/xml
AWSAccessKeyIdやらのパラメータ

の方が良さそう。さらに、AssociateTagパラメータがあると必ずエラーが返ってくるようなので除外。エラーにはなりませんが、私はSubscriptionIdも除外しました。

 なお、Perlでの変更の要点は、以下の通り。一部前述と重複します。なお、リクエストパラメータは、既にURIエスケープされているものとします。

 Digest::SHAのhmac_sha256_base64を使う。

use Digest::SHA qw(hmac_sha256_base64);

 タイムスタンプは「yyyy-mm-ddThh:mm:ssZ」形式。次のサブルーチンで生成できます。

sub gettimestamp {
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
    my $ts = sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ",$year+1900,$mon+1,$mday,$hour,$min,$sec);
    $ts =~ s/:/%3A/g;
    return $ts;
}

 署名を作るサブルーチン。シークレットアクセスキーを「0123456789」と仮定。

sub getsignature {
    my($sortedparams) = @_;
    my $data      = "GET\n"."ecs.amazonaws.jp\n"."/onca/xml\n".$sortedparams;
    my $key       = '0123456789';
    my $signature = hmac_sha256_base64($data, $key);

    $signature .= '=' while length($signature) % 4;

    return URI::Escape::uri_escape($signature);
}

 Webサービスへのリクエストパラメータは、アルファベット順にソートしておく。

my $timestamp = gettimestamp();
my $sortedparams = "AWSAccessKeyId=........&ItemPage=1&Keywords=.......&Operation=ItemSearch&ResponseGroup=Medium&SearchIndex=.......&Service=AWSECommerceService&Timestamp=$timestamp&Version=2009-01-06";

 このサブルーチンを呼び出して、Signatureパラメータとしてリクエストに加えます。

my $signature = getsignature($sortedparams);
my $requesturl = "http://ecs.amazonaws.jp/onca/xml?$sortedparams&Signature=".$signature;

 これで、リクエストが可能になります。