itsmeevg
2/17/2020 - 1:01 PM

AWS S3: Pre-sign Upload & Download Requests [PHP]

AWS S3: Pre-sign Upload & Download Requests [PHP]

<?php

function AWS_S3_PresignDownload($AWSAccessKeyId, $AWSSecretAccessKey, $BucketName, $AWSRegion, $canonical_uri, $expires = 8400) {
    // Creates a signed download link for an AWS S3 file
    // Based on https://gist.github.com/kelvinmo/d78be66c4f36415a6b80

    $encoded_uri = str_replace('%2F', '/', rawurlencode($canonical_uri));

    // Specify the hostname for the S3 endpoint
    if($AWSRegion == 'us-east-1') {
        $hostname = trim($BucketName .".s3.amazonaws.com");
        $header_string = "host:" . $hostname . "\n";
        $signed_headers_string = "host";
    } else {
        $hostname =  trim($BucketName . ".s3-" . $AWSRegion . ".amazonaws.com");
        $header_string = "host:" . $hostname . "\n";
        $signed_headers_string = "host";
    }
    
    $date_text = gmdate('Ymd', time());
    $time_text = $date_text . 'T000000Z';
    $algorithm = 'AWS4-HMAC-SHA256';
    $scope = $date_text . "/" . $AWSRegion . "/s3/aws4_request";
    
    $x_amz_params = array(
        'X-Amz-Algorithm' => $algorithm,
        'X-Amz-Credential' => $AWSAccessKeyId . '/' . $scope,
        'X-Amz-Date' => $time_text,
        'X-Amz-SignedHeaders' => $signed_headers_string
    );
    
    if ($expires > 0) {
        // 'Expires' is the number of seconds until the request becomes invalid
        $x_amz_params['X-Amz-Expires'] = $expires;
    }
    
    ksort($x_amz_params);
    
    $query_string = "";
    foreach ($x_amz_params as $key => $value) {
        $query_string .= rawurlencode($key) . '=' . rawurlencode($value) . "&";
    }
    $query_string = substr($query_string, 0, -1);
    
    $canonical_request = "GET\n" . $encoded_uri . "\n" . $query_string . "\n" . $header_string . "\n" . $signed_headers_string . "\nUNSIGNED-PAYLOAD";
    $string_to_sign = $algorithm . "\n" . $time_text . "\n" . $scope . "\n" . hash('sha256', $canonical_request, false);
    $signing_key = hash_hmac('sha256', 'aws4_request', hash_hmac('sha256', 's3', hash_hmac('sha256', $AWSRegion, hash_hmac('sha256', $date_text, 'AWS4' . $AWSSecretAccessKey, true), true), true), true);
    $signature = hash_hmac('sha256', $string_to_sign, $signing_key);
    
    return 'https://' . $hostname . $encoded_uri . '?' . $query_string . '&X-Amz-Signature=' . $signature;

}

?>
<?php

function AWS_S3_hmac_sha256($key, $msg, $binary = true) {
    return hash_hmac("sha256", $msg, $key, $binary);
}

function AWS_S3_PresignUpload($BucketName, $AWSAccessKeyId, $AWSSecretAccessKey, $AWSRegion, $UploadFilenameStartsWith) {
    /* Function to presign an AWS S3 file upload.
    
       This method of uploading can allow clients to securely upload files
       directly to S3, while ensuring certian conditions are enforced (e.g. upload filename)
       
       Written by Anthony Eden http://mediarealm.com.au/
    */
    
    $AWSService = "s3";
    $AWSRequest = "aws4_request";
    $date = date("Ymd");
    
    $AWSPolicy = '{ "expiration": "'.gmdate("Y-m-d", strtotime("tomorrow")).'T12:00:00.000Z",
    "conditions": [
        {"bucket": "'.$BucketName.'"},
        ["starts-with", "$key", "'.$UploadFilenameStartsWith.'"],
        {"x-amz-server-side-encryption": "AES256"},
        {"x-amz-credential": "'.$AWSAccessKeyId.'/'.$date.'/'.$AWSRegion.'/'.$AWSService.'/'.$AWSRequest.'"},
        {"x-amz-algorithm": "AWS4-HMAC-SHA256"},
        {"x-amz-date": "'.$date.'T000000Z" }
    ]
    }';
    
    $StringToSign = base64_encode($AWSPolicy);
    
    $DateKey = AWS_S3_hmac_sha256("AWS4" . $AWSSecretAccessKey, $date);
    $DateRegionKey = AWS_S3_hmac_sha256($DateKey, $AWSRegion);
    $DateRegionServiceKey = AWS_S3_hmac_sha256($DateRegionKey, $AWSService);
    $SigningKey = AWS_S3_hmac_sha256($DateRegionServiceKey, $AWSRequest);
    
    $Signature = AWS_S3_hmac_sha256($SigningKey, $StringToSign, false);
    
    return array(
        "BucketName" => $BucketName,
        "KeyPrefix" => $UploadFilenameStartsWith,
        "x-amz-server-side-encryption" => "AES256",
        "X-Amz-Credential" => $AWSAccessKeyId.'/'.$date.'/'.$AWSRegion.'/'.$AWSService.'/'.$AWSRequest,
        "X-Amz-Algorithm" => "AWS4-HMAC-SHA256",
        "X-Amz-Date" => $date.'T000000Z',
        "Policy" => $StringToSign,
        "X-Amz-Signature" => $Signature
    );
}

?>
import os
import requests

def uploadS3(srcFilename, S3Upload):
    # This method uploads a file to a S3 bucket using pre-signed credentials
    # S3Upload is a dictionary returned by AWS_S3_Presign_Upload.php

    # Determine the extension of the original file
    filename, file_extension = os.path.splitext(srcFilename)

    # Perform the upload
    r = requests.post(
        'http://' + S3Upload['BucketName'] + '.s3.amazonaws.com/',
        files = {
            'file': open(srcFilename, 'rb')
        },
        data = {
            "key": S3Upload['KeyPrefix'] + file_extension,
            "x-amz-server-side-encryption": S3Upload['x-amz-server-side-encryption'],
            "X-Amz-Algorithm": S3Upload['X-Amz-Algorithm'],
            "X-Amz-Credential": S3Upload['X-Amz-Credential'],
            "X-Amz-Date": S3Upload['X-Amz-Date'],
            "Policy": S3Upload['Policy'],
            "X-Amz-Signature": S3Upload['X-Amz-Signature']
        }
    )
    
    if r.status_code == 200 or r.status_code == 204:
        # Success!
        return True
    else:
        # Debug output
        print "ERROR: Cannot upload file to S3", srcFilename
        print r.status_code, r.reason
        print r.text
        return False