PHP で Amazon S3 の REST API を使用 #1

Amazon S3PHP から操作する場合、AWS SDK for PHP を使用するのが簡単かつ確実です。

しかし、WordPress のプラグインなどを作成する場合には、WordPress のライセンスである GNU General Public License version 2.0 と AWS SDK for PHP のライセンスである Apache License 2.0 との間で矛盾が生じてしまいます。(矛盾する理由の詳細は、記事末尾の参考文献を参照してください。)

そこで、今回は Amazon S3 の REST API を PHP で Client URL Library を使用して呼び出すことで、Amazon S3 を操作してみます。

それでは、第1回目は Amazon S3 の REST API で使用されている認証を実装してみようと思います。なお、この記事に記載しているコードは、パブリックドメインということにしておきますので、使用される方の責任でご自由にお使いください。

概要

Amazon S3 の REST API での認証の概要は下記の2枚の図のようになります。これらの図は、Amazon S3 Developer Guide から引用してきたものです。

HMAC Authentication Process (You)

HMAC Authentication Process (You)

  1. AWS へのリクエストを作成します。
  2. シークレットアクセスキーを使用して署名を計算します。
  3. Amazon S3 に、アクセスキー ID と署名を含めたリクエストを送信します。
HMAC Authentication Process (AWS)

HMAC Authentication Process (AWS)

  1. Amazon S3 は、アクセスキー ID をもとにシークレットアクセスキーを検索します。
  2. リクエストデータとシークレットアクセスキーを用い、クライアント側と同様のアルゴリズムで署名を計算します。
  3. Amazon S3 側で計算した署名と、リクエストデータとして送られてきた署名を比較し、一致すれば信頼できるリクエストとして受理しますが、不一致であれば棄却します。

このような処理をリクエストごとに実施することになるわけです。

アクセスキー ID とシークレットアクセスキー

アクセスキー ID とシークレットアクセスキーは、 AWS サイトのグローバルメニュー「アカウント/コンソール」のセキュリティ証明書をクリックしてログインしたページから取得できます。

AWS Management Console | アマゾン ウェブ サービス(AWS 日本語)

AWS Management Console | アマゾン ウェブ サービス(AWS 日本語)

ログイン後、セキュリティ証明書のページで、アクセスキータブ内のアクセスキー ID とシークレットアクセスキーをコピーしておいてください。

Amazon Web サービス

Amazon Web サービス

これで、認証に必要なアクセスキー ID とシークレットアクセスキーを取得することができました。

署名

署名は、あるアクセスキー ID を含む HTTP リクエストの内容が、そのアクセスキー ID の所有者から送信されたものであることを証明する文字列です。アクセスキー ID の所有者というのは、シークレットアクセスキーを所有している者ということになります。

重要なことは、シークレットアクセスキーを直接送らないことです。暗号化された HTTPS 通信だけではなく、平文の HTTP 通信でも REST API を実行できるのには、この署名を使用しているからです。

署名のポイントは2つです。

  • 「HTTP リクエストの内容」を「シークレットアクセスキー」で変換した文字列であること
  • 署名から「シークレットアクセスキー 」を取得できないこと

これで、リクエストの内容を証明することができるわけです。概要で説明した図をもう一度見てみるとわかりやすいと思います。

署名生成

それでは、実際に署名を PHP で生成してみます。

概要で示した図のとおり、クライアント側から送信されてきた署名をサーバー側(AWS 側)でも同じように生成して一致を判定します。そのため、クライアント側とサーバー側で同じルールを使用しなければなりません。そのルールは下記の通りです。これは Amazon S3 Developer Guide から引用しています。

Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );

StringToSign = HTTP-Verb + "\n" +
	Content-MD5 + "\n" +
	Content-Type + "\n" +
	Date + "\n" +
	CanonicalizedAmzHeaders +
	CanonicalizedResource;

CanonicalizedResource = [ "/" + Bucket ] +
	<HTTP-Request-URI, from the protocol name up to the query string> +
	[ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"];

CanonicalizedAmzHeaders = <described below>

このように見ると複雑ですが、今回はアクセスキー所有者のバケット一覧を取得する GET Service をリクエストするため、ほとんどの項目は改行のみの文字列や空文字列になります。

  • HTTP-Verb: “GET\n”
  • Content-MD5: “\n”
    ※ PUT のときに必要です。
  • Content-Type: “\n”
    ※ PUT のときに必要です。
  • Date: RFC 1123 形式の現在協定世界時 + “\n”
  • CanonicalizedAmzHeaders: “”
    ※ AWS 用の特別な HTTP ヘッダーを使用するときに必要です。
  • CanonicalizedResource: “/”

これで各項目が決定できたので、実際のスクリプトにしてみます。スクリプトにすれば一目瞭然かと思います。

// ----- Signature -----
// HTTP-Verb
$httpVerb = 'GET' . "\n";

// Content-MD5
$contentMd5 = "\n";

// Content-Type
$contentType = "\n";

// Date
$datetime = new DateTime('now', new DateTimeZone('UTC'));
$date = $datetime->format(DateTime::RFC1123) . "\n";

// CanonicalizedAmzHeaders
$canonicalizedAmzHeaders = '';

// CanonicalizedResource
$canonicalizedResource = '/';

// StringToSign
$stringToSign = $httpVerb . $contentMd5 . $contentType . $date
              . $canonicalizedAmzHeaders . $canonicalizedResource;

// Signature
$hash = hash_hmac('sha1', $stringToSign, $secretAccessKey, true);
$signature = base64_encode($hash);

ここで注意してもらいたいことがふたつあります。

ひとつは、ファイルのエンコーディングを UTF-8 にすることです。引用した署名生成のルールに UTF-8-Encoding-Of 関数が記載されているとおり、 SHA-1 でハッシングする前の文字列のエンコーディングは、UTF-8 でなければなりません。なので、ファイルのエンコーディングを UTF-8 にするのが無難かと思います。

もうひとつは、hash_hmac 関数の第4引数を true にすることです。第4引数はデフォルト false で、false の場合は小文字の16 進数文字列で結果を返し、true の場合は生のバイナリデータで結果を返します。

蛇足ですが、Mac OS X の NetBeans で作業する場合は、\ と ¥ の違いに注意してください。詳細は、NetBeans でバックスラッシュを入力を参照してください。

これで、$signature 変数には、GET Service をリクエストするための署名が入りました。

Authorization ヘッダー

次に、HTTP リクエストに署名をつける必要があります。そのために、Authorization ヘッダーを使用します。

アクセスキー ID (AWSAccessKeyId) と前段で生成した署名 (Signature) を下記のような形式で HTTP ヘッダーに追加します。

Authorization: AWS AWSAccessKeyId:Signature

これをスクリプトにすると下記のようになります。

// ----- Authorization Header -----
// Authorization
$authorization = 'AWS' . ' ' . $accessKeyId . ':' . $signature;

これで、Authorization ヘッダー用文字列が完成します。

HTTP リクエスト生成

それでは、Amazon S3 に GET Service をリクエストしてみます。HTTP リクエストの生成には、PHP の Client URL Library を使用します。スクリプトにすると下記のようになります。今回は、ホストとして s3.amazonaws.com を指定しています。ホストは、バケットに関わってくるので、バケットの作成や削除のときに説明します。また、HTTP を使用していますが、HTTPS も使用できます。

// ----- HTTP Request -----
// Gets list of buckets.
$ch = curl_init('http://s3.amazonaws.com');
$headers = array(
    'Authorization: ' . $authorization,
    'Date: ' . $date,
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = curl_exec($ch);
curl_close($ch);

注意することとしては、Date ヘッダーも必要になることです。Date ヘッダーの内容は、署名を計算したときに使用したものをそのまま使用してください。

なお、各関数については、参考文献に記載したマニュアルを参照いただければと思います。

これで、$body 変数に、アクセスキー所有者のバケット一覧の XML が格納されます。エラーの場合は、エラー情報が含まれた XML が格納されます。エラーの場合は、その XML の内容をもとに、スクリプトを確認してみてください。

今回は、認証の実装が目的なので、XML の内容の詳細は割愛させていただきます。次回は、バケットの作成や削除を REST API で実装してみようと思います。

参考文献

About TSUCHIDA Takuya

生まれ変わったら黒猫になりたいシステムアーキテクトです。僕への連絡は右下の MessageLeaf からお願いします。
This entry was posted in Amazon S3 and tagged , , , , . Bookmark the permalink.