Amazon S3 PHP helpers

The Amazon documentation for using S3 with PHP refers to an elusive function called “setAuthorizationHeader”. It’s apparently supposed to magically set the correct value for the Authorization header on a Pear HTTP_Request object. As far as I could tell, it didn’t actually exist — but I wanted it, so I wrote it:

Source

<?php

require_once ‘Crypt/HMAC.php’;
require_once ‘HTTP/Request.php’;

define("S3URL", ‘http://s3.amazonaws.com’);
define("AWSACCESSKEYID", ‘XXXXXXXXXXXXXXXXXXXX’);
define("AWSSECRETKEYID", ‘XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX’);

$s3_hasher =& new Crypt_HMAC(AWSSECRETKEYID, "sha1");

function s3_sign($StringToSign)
{
    global $s3_hasher;
    return hex2b64($s3_hasher->hash($StringToSign));
}

function hex2b64($str)
{
    $raw = ;
    for ($i = 0; $i < strlen($str); $i += 2) {
        $raw .= chr(hexdec(substr($str, $i, 2)));
    }
    return base64_encode($raw);
}

function setAuthorizationHeader($request)
{
    $headers = $request->_requestHeaders;
    
    $HTTP_Verb      = $request->_method;
    $Content_MD5    = $headers[‘content-md5’];
    $Content_Type   = $headers[‘content-type’];

    // Get the date, or set it if not already:
    if (!isset($headers[‘date’])) {
        $Date = gmdate("D, d M Y H:i:s T");
        $request->addHeader("date", $Date);
    }
    else {
        $Date = $headers[‘date’];
    }
    
    // Canonicalize the Amazon headers:
    $CanonicalizedAmzHeaders = ;
    $amz_headers = array();
    foreach ($headers as $key => $value) {
        if (substr($key, 0, 6) == ‘x-amz-‘) {
            if (isset($amz_headers[$key]))
                $amz_headers[$key] .= ‘,’ . $value;
            else
                $amz_headers[$key] = $value;
        }
    }
    ksort($amz_headers);
    foreach ($amz_headers as $key => $value)
        $CanonicalizedAmzHeaders .= $key . ‘:’ . $value . "\n";
    
    // Canonicalize the resource string
    $CanonicalizedResource    = ;
    $host = $request->_generateHostHeader();
    if ($host != ‘s3.amazonaws.com’) {
        $pos = strpos($host, ‘s3.amazonaws.com’);
        $CanonicalizedResource .= ‘/’ . ($pos === false) ? $host : substr($host, 0, $pos);
    }
    $CanonicalizedResource .= $request->_url->path;
    // TODO: sub-resources "?acl", "?location", "?logging", or "?torrent"

    // Build the string to sign:
    $StringToSign = $HTTP_Verb . "\n" .
                    $Content_MD5 . "\n" .
                    $Content_Type . "\n" .
                    $Date . "\n" .
                    $CanonicalizedAmzHeaders .
                    $CanonicalizedResource;
    
    $Signature = s3_sign($StringToSign);
    
    $Authorization = "AWS" . " " . AWSACCESSKEYID . ":" . $Signature;
    
    // Set the Authorization header:
    $request->addHeader("Authorization", $Authorization);
}

function s3AuthURL($Resource, $HTTP_Verb = ‘GET’, $seconds = 120)
{
    // Calculate expiration time:
    $Expires = time() + $seconds;

    // Build the string to sign:
    $StringToSign = $HTTP_Verb . "\n" .
                    "\n" .
                    "\n" .
                    $Expires . "\n" .
                    $Resource;

    $Signature = s3_sign($StringToSign);

    // Build the authorized URL:
    return  S3URL . $Resource .
            ‘?AWSAccessKeyId=’  . AWSACCESSKEYID .
            ‘&Expires=’         . $Expires .
            ‘&Signature=’       . urlencode($Signature);
}

?>

Note: this hasn’t been tested extensively, so use it at your own risk. Post a comment or contact me at if you find any bugs. Also, IANAPHPE.

Just replace the XX’s with your keys and it should work with this sample code.

There’s also a function for creating query string authorized URLs.