-
-
Save anthonyeden/4448695ad531016ec12bcdacc9d91cb8 to your computer and use it in GitHub Desktop.
<?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 |
Tried download code but it is not working and saying Access Expired. Have solved it using following code
function AWS_S3_PresignDownload($AWSAccessKeyId, $AWSSecretAccessKey, $BucketName, $AWSRegion, $canonical_uri, $expires = 8400)
{
$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";
}
$currentTime = time();
$date_text = gmdate('Ymd', $currentTime);
$time_text = $date_text . 'T' . gmdate('His', $currentTime) . 'Z';
$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
);
// 'Expires' is the number of seconds until the request becomes invalid
$x_amz_params['X-Amz-Expires'] = $expires + 30; // 30seocnds are less
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;
}
@anthonyeden Any way to have a custom filename on download?
My Question: https://stackoverflow.com/questions/65407056/aws-s3-authenticating-requests-using-query-parameters-to-download-file-with-cust
As @umair321 noted above the original code has the time hardcoded, so if you set the expiry time < 24 hours it will not work, depending on the time of day when you run it. Also, because it uses the <bucketname>
.s3.<region>
.amazonaws.com URLs, it does not work if your bucketname contains dots.
Where did you find the specification for the implementation?
Where did you find the specification for the implementation?
To be honest @jacobemcken, I cannot remember - it was 6+ years ago! It was likely a mix of Stack Overflow and the official SDKs. I think there's a new version of the protocol out now, and I think you'd be better off using the SDK rather than re-implementing.
I found some relevant documentation here:
https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
Thanks a lot!