Amazon S3 を PHP から操作する場合、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 から引用してきたものです。
- AWS へのリクエストを作成します。
- シークレットアクセスキーを使用して署名を計算します。
- Amazon S3 に、アクセスキー ID と署名を含めたリクエストを送信します。
- Amazon S3 は、アクセスキー ID をもとにシークレットアクセスキーを検索します。
- リクエストデータとシークレットアクセスキーを用い、クライアント側と同様のアルゴリズムで署名を計算します。
- Amazon S3 側で計算した署名と、リクエストデータとして送られてきた署名を比較し、一致すれば信頼できるリクエストとして受理しますが、不一致であれば棄却します。
このような処理をリクエストごとに実施することになるわけです。
アクセスキー ID とシークレットアクセスキー
アクセスキー ID とシークレットアクセスキーは、 AWS サイトのグローバルメニュー「アカウント/コンソール」のセキュリティ証明書をクリックしてログインしたページから取得できます。
ログイン後、セキュリティ証明書のページで、アクセスキータブ内のアクセスキー ID とシークレットアクセスキーをコピーしておいてください。
これで、認証に必要なアクセスキー 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: AWSAWSAccessKeyId
: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 で実装してみようと思います。
参考文献