Boto3: S3 generate_presigned_url is not working with md5 hash

Created on 29 Dec 2016  路  5Comments  路  Source: boto/boto3

The pre-signed url is not working with md5 hash.
Normally when put

params = {'Bucket': bucket_name, 'Key': key}
url = s3_client.generate_presigned_url('put_object',
                                               Params=params,
                                               ExpiresIn=3600)

it works.

But when put any other put_object parameter like ACL md5 etc it throws

params = {'Bucket': bucket_name, 'Key': key, 
                  'ACL': 'public-read', 'ContentMD5': '1cx12....'}
url = s3_client.generate_presigned_url('put_object',
                                               Params=params,
                                               ExpiresIn=3600)
Error><Code>SignatureDoesNotMatch</Code><Message>
The request signature we calculated does not match the 
signature you provided. Check your key and signing method

If trying put header like Content-MD5 in put request it throws Headers not signed.
My S3 client is like

s3 = boto3.client('s3',
                      region_name=app.config['AWS_REGION'],
                      aws_access_key_id=app.config['AWS_ACCESS_KEY_ID'],
                      config=Config(signature_version='s3v4'),
                      aws_secret_access_key=app.config['AWS_SECRET_ACCESS_KEY']
                      )

I am using boto3==1.4.1

closing-soon question

Most helpful comment

@JordonPhillips thanks a lot, it is working. I am posting the working example for future reference.

import boto3
import requests

parts = s3_client.generate_presigned_post(Bucket=bucket_name,
                                              Key=key,
                                              Fields={
                                                'acl': 'public-read',
                                                'Content-MD5': str(md5),
                                                'Content-Type': 'binary/octet-stream'
                                                },
                                              Conditions=[
                                                  {"acl": "public-read"},
                                                  ["starts-with", "$Content-Type", ""],
                                                  ["starts-with", "$Content-MD5", ""]
                                              ]
                                          )

url = parts['url']
data = parts['fields']
files = {'file': open(local_path, 'rb')} # the key supposed to be file may be
response = requests.post(url, data=data, files=files)

Closing the issue

All 5 comments

For uploading an object, you should use generate_presigned_post. There are several parameters that cannot be embedded within the url, and those are returned to you by that method.

Hi thanks for your reply. Today i was trying with generate_presigned_post

s3_client = boto3.client('s3',
                      region_name=app.config['AWS_REGION'],
                      aws_access_key_id=app.config['AWS_ACCESS_KEY_ID'],
                      config=Config(signature_version='s3v4'),
                      aws_secret_access_key=app.config['AWS_SECRET_ACCESS_KEY']
                      )
p = s3_client.generate_presigned_post(Bucket=bucket_name,
                                              Key=key,
                                              Fields={
                                                'acl': 'public-read'
                                                }
                                          )

url = p['url']
data = p['fields']
files = {'path': "YOYO"}
response = requests.post(url, json=data, data=open(file))

I am getting 412 response

<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>PreconditionFailed</Code>
<Message>At least one of the pre-conditions you specified did not hold</Message>
<Condition>Bucket POST must be of the enclosure-type multipart/form-data</Condition>
<RequestId>E5D751C1E8E3A187</RequestId>
<HostId>LWL4KO3n7Xotvef4x+1bvAk6XeHWa0sSMWluHkUP1zyNPBbFyzFnCIWANmSshuYnlkbDL+AXir4=</HostId>
</Error>

When i was doing, what is written in boto3 doc

p = s3_client.generate_presigned_post(Bucket=bucket_name,
                                              Key=key,
                                              Fields={
                                                'acl': 'public-read'
                                                }
                                          )
url = p['url']
data = p['fields']
files = {local_path: "YOYO"}
response = requests.post(url, data=data, files=files)

I am getting 400

<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>InvalidArgument</Code>
<Message>POST requires exactly one file upload per request.</Message>
<ArgumentName>file</ArgumentName>
<ArgumentValue>0</ArgumentValue>
<RequestId>4122DAA6E127CC6E</RequestId>
<HostId>SRRp+j+298weagOqsfIEAzMpVoNKKh4u5tPJmf5DAa9WeDak3bs8Ne+Nq1NMX8hkOyLENFxrSVU=</HostId>
</Error>

Maybe that's not the correct way to use files (the requests docs isn't super clear on this point). See below for a working sample.

import boto3
import requests


s3 = boto3.client('s3')
parts = s3.generate_presigned_post(Bucket='foo', Key='bar')
response = requests.post(parts['url'], data=parts['fields'],
                         files={'file': open('some_filename.txt', 'rb')})

@JordonPhillips thanks a lot, it is working. I am posting the working example for future reference.

import boto3
import requests

parts = s3_client.generate_presigned_post(Bucket=bucket_name,
                                              Key=key,
                                              Fields={
                                                'acl': 'public-read',
                                                'Content-MD5': str(md5),
                                                'Content-Type': 'binary/octet-stream'
                                                },
                                              Conditions=[
                                                  {"acl": "public-read"},
                                                  ["starts-with", "$Content-Type", ""],
                                                  ["starts-with", "$Content-MD5", ""]
                                              ]
                                          )

url = parts['url']
data = parts['fields']
files = {'file': open(local_path, 'rb')} # the key supposed to be file may be
response = requests.post(url, data=data, files=files)

Closing the issue

It appears that the key used in the files argument dict must be 'file'.

Was this page helpful?
0 / 5 - 0 ratings