Python编写AWS Version 4 signing (AWS4-HMAC-SHA256) for execute-api

官网教程中给了签署AWS请求给了详细的介绍和python的例子,但是例子针对DynamoDB API,本例子针对API Gateway的POST请求,并携带有x-amz-security-token、x-amz-content-sha256两个头。
例子中经常缺乏这些细节的具体处理,特此记录。最后要注意发送的请求URL是否正确。
# AWS Version 4 signing example

# DynamoDB API (CreateTable)

# See: http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
# This version makes a POST request and passes request parameters
# in the body (payload) of the request. Auth information is passed in
# an Authorization header.

# POST /dev/v1/user/associations/robots/3147C60040239620 HTTP/1.1
# X-Amz-Date: 20180502T073321Z
# Host: njc3321216.execute-api.us-east-1.amazonaws.com
# x-amz-security-token: AgoGb3JpZ2luENP//////////wEaCXVzLWVhc3QtMSKAAhZ6pzVklfasnpF7+uhEzpnvrRzgXRgFqXvrPWkjtJamALYtWvG0A9sYuKF+b2Jxpitq3UUoatmH1LJtOS3iSXM8RpjiElYd8OZMGqBs7o8Z/7QJV69NUYzKewcVwWrv1HJHGmwTUR/8Oqs7bI9KibLAJclCFBYMgodLd8eYW3m9Oq4kAudQBB7vVQ4hyiGErvwAayA5SqtIvpkr3tR19nH0o57BJHyVNXMuYOKzhPquzxMuuV/wv1XXPGeH+qZNT1a6XAhFZ0LdHmi9A2TgrrmJhlsjvCfsvSA3phBqJhiWNHuM1JHNFF6Vcwg+UV4FdY4qwHP82WPQjiamgiPasEkq9QQIKRAAGgwzMzM0NDAzODkyNDYiDAB/KfmZzmMqsJAwJirSBCTMCqIcagKhuyDzw+1dvIIckZKY5haYIoOB3siUUy7gj2JTTYhxH2GRUSFdPduM05hglqvDHS6L9SyUwctgQ+fBjRIur4k9YtBrWJBI+KFVMEV5UyMr91V0LAMmzQDJfLzIC7av7hEy9LJMNt4axZyrzfKgXqiW2dhmABbREvq/6oBQKIk4x7NbnmOqoCHPfna6Y1QMGXjzZmzG1UYY/u24cyaARnQrMVGmnEPK3hum8RY/Zf/0JlTmz3sapSiRPOhIiDBGg0Cu0mq/arPu1n/nhf6mWdFgWd2pRg3H1WUk5SUystQrBlr1CxnUqQeSEE+IQp9+0HhYdeaMXtEA1V4Ju6ET2p696TZrssxtcd+xoeHUdiq7Rer1QZx04abXVKLVsL0t5KyASNCfLQKbwNBm6su1n2tpqKxdGWt3PmHKTUicOxSpJ+7w7/VVAkyEg3xfCexb7+H2cdhcTKrMvp1225lHkbyW69dgUrdMREvXNyOpu2ludhRZNxZFICCBNKI4xpwZ1PcLq0Wr2H3Uln4GxCTIomx5a7An6dcW6bogzEYTGhZYzEhgTY7GbqAG59zLzJ/NtbZqmYQC4adWolh37OibYuRffkVsDXYFLpJ1qCAw+y7wnwYELCD4QX+VRSOpArYDnmaJp338RjqZSE593LIAR9CR3ihaFsUdFPzRX6LqHCQq7VWA+FVQJCsgHxlcU8+ShHcv0jh745ClcMwi4mCZl1fO1L9HupnobVCXodRwaXJ1ZSDGlSG/qr5BevbKn5KRybg7CHBgcIMwhMfkODDB06XXBQ==
# Content-type: application/json; charset=utf-8
# x-amz-content-sha256: d96401ecabf9143f5c90fe37ba177f0de647314bcfd49c698acb132f80749212
# Accept: application/json; charset=utf-8
# Authorization: AWS4-HMAC-SHA256 Credential=ASIAJCV4GTJQPYRAYKYQ/20180502/us-east-1/execute-api/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=3bd5e785b3e5110bb4a53b375013ec6888a8fe51ff3f4fa2b05e17ce4457896b
# Content-Length: 46
# User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.0; Nexus 9 Build/NRD90M)
# Connection: Keep-Alive
# Accept-Encoding: gzip
#{"password": ":1:1525246193:5aR5eYKn6zsQ6vIp"}


# {
#     "credentials": {
#         "SecretKey": "JJ+BouOzY53lEkJMuQ/w/PtWQaCgo32sEwzD4mw+",
#         "SessionToken": "AgoGb3JpZ2luENP//////////wEaCXVzLWVhc3QtMSKAAhZ6pzVklfasnpF7+uhEzpnvrRzgXRgFqXvrPWkjtJamALYtWvG0A9sYuKF+b2Jxpitq3UUoatmH1LJtOS3iSXM8RpjiElYd8OZMGqBs7o8Z/7QJV69NUYzKewcVwWrv1HJHGmwTUR/8Oqs7bI9KibLAJclCFBYMgodLd8eYW3m9Oq4kAudQBB7vVQ4hyiGErvwAayA5SqtIvpkr3tR19nH0o57BJHyVNXMuYOKzhPquzxMuuV/wv1XXPGeH+qZNT1a6XAhFZ0LdHmi9A2TgrrmJhlsjvCfsvSA3phBqJhiWNHuM1JHNFF6Vcwg+UV4FdY4qwHP82WPQjiamgiPasEkq9QQIKRAAGgwzMzM0NDAzODkyNDYiDAB/KfmZzmMqsJAwJirSBCTMCqIcagKhuyDzw+1dvIIckZKY5haYIoOB3siUUy7gj2JTTYhxH2GRUSFdPduM05hglqvDHS6L9SyUwctgQ+fBjRIur4k9YtBrWJBI+KFVMEV5UyMr91V0LAMmzQDJfLzIC7av7hEy9LJMNt4axZyrzfKgXqiW2dhmABbREvq/6oBQKIk4x7NbnmOqoCHPfna6Y1QMGXjzZmzG1UYY/u24cyaARnQrMVGmnEPK3hum8RY/Zf/0JlTmz3sapSiRPOhIiDBGg0Cu0mq/arPu1n/nhf6mWdFgWd2pRg3H1WUk5SUystQrBlr1CxnUqQeSEE+IQp9+0HhYdeaMXtEA1V4Ju6ET2p696TZrssxtcd+xoeHUdiq7Rer1QZx04abXVKLVsL0t5KyASNCfLQKbwNBm6su1n2tpqKxdGWt3PmHKTUicOxSpJ+7w7/VVAkyEg3xfCexb7+H2cdhcTKrMvp1225lHkbyW69dgUrdMREvXNyOpu2ludhRZNxZFICCBNKI4xpwZ1PcLq0Wr2H3Uln4GxCTIomx5a7An6dcW6bogzEYTGhZYzEhgTY7GbqAG59zLzJ/NtbZqmYQC4adWolh37OibYuRffkVsDXYFLpJ1qCAw+y7wnwYELCD4QX+VRSOpArYDnmaJp338RjqZSE593LIAR9CR3ihaFsUdFPzRX6LqHCQq7VWA+FVQJCsgHxlcU8+ShHcv0jh745ClcMwi4mCZl1fO1L9HupnobVCXodRwaXJ1ZSDGlSG/qr5BevbKn5KRybg7CHBgcIMwhMfkODDB06XXBQ==",
#         "Expiration": "2018-05-02T08:33:21+00:00",
#         "CognitoId": "us-east-1:c9c81ec4-1c82-4e0c-889d-34df3d144f5a",
#         "AccessKeyId": "ASIAJCV4GTJQPYRAYKYQ"
#     }
# }

import sys, os, base64, datetime, hashlib, hmac 
import requests # pip install requests

# ************* REQUEST VALUES *************
method = 'POST'
service = 'execute-api'
host = 'njc3321216.execute-api.us-east-1.amazonaws.com'
region = 'us-east-1'
endpoint = 'https://njc3321216.execute-api.us-east-1.amazonaws.com'
amz_security_token = 'AgoGb3JpZ2luENP//////////wEaCXVzLWVhc3QtMSKAAhZ6pzVklfasnpF7+uhEzpnvrRzgXRgFqXvrPWkjtJamALYtWvG0A9sYuKF+b2Jxpitq3UUoatmH1LJtOS3iSXM8RpjiElYd8OZMGqBs7o8Z/7QJV69NUYzKewcVwWrv1HJHGmwTUR/8Oqs7bI9KibLAJclCFBYMgodLd8eYW3m9Oq4kAudQBB7vVQ4hyiGErvwAayA5SqtIvpkr3tR19nH0o57BJHyVNXMuYOKzhPquzxMuuV/wv1XXPGeH+qZNT1a6XAhFZ0LdHmi9A2TgrrmJhlsjvCfsvSA3phBqJhiWNHuM1JHNFF6Vcwg+UV4FdY4qwHP82WPQjiamgiPasEkq9QQIKRAAGgwzMzM0NDAzODkyNDYiDAB/KfmZzmMqsJAwJirSBCTMCqIcagKhuyDzw+1dvIIckZKY5haYIoOB3siUUy7gj2JTTYhxH2GRUSFdPduM05hglqvDHS6L9SyUwctgQ+fBjRIur4k9YtBrWJBI+KFVMEV5UyMr91V0LAMmzQDJfLzIC7av7hEy9LJMNt4axZyrzfKgXqiW2dhmABbREvq/6oBQKIk4x7NbnmOqoCHPfna6Y1QMGXjzZmzG1UYY/u24cyaARnQrMVGmnEPK3hum8RY/Zf/0JlTmz3sapSiRPOhIiDBGg0Cu0mq/arPu1n/nhf6mWdFgWd2pRg3H1WUk5SUystQrBlr1CxnUqQeSEE+IQp9+0HhYdeaMXtEA1V4Ju6ET2p696TZrssxtcd+xoeHUdiq7Rer1QZx04abXVKLVsL0t5KyASNCfLQKbwNBm6su1n2tpqKxdGWt3PmHKTUicOxSpJ+7w7/VVAkyEg3xfCexb7+H2cdhcTKrMvp1225lHkbyW69dgUrdMREvXNyOpu2ludhRZNxZFICCBNKI4xpwZ1PcLq0Wr2H3Uln4GxCTIomx5a7An6dcW6bogzEYTGhZYzEhgTY7GbqAG59zLzJ/NtbZqmYQC4adWolh37OibYuRffkVsDXYFLpJ1qCAw+y7wnwYELCD4QX+VRSOpArYDnmaJp338RjqZSE593LIAR9CR3ihaFsUdFPzRX6LqHCQq7VWA+FVQJCsgHxlcU8+ShHcv0jh745ClcMwi4mCZl1fO1L9HupnobVCXodRwaXJ1ZSDGlSG/qr5BevbKn5KRybg7CHBgcIMwhMfkODDB06XXBQ=='
# POST requests use a content type header. For DynamoDB,
# the content is JSON.
content_type = 'application/json'
# DynamoDB requires an x-amz-target header that has this format:
#     DynamoDB_<API version>.<operationName>
#amz_target = 'DynamoDB_20120810.CreateTable'

# Request parameters for CreateTable--passed in a JSON block.
request_parameters =  '{'
request_parameters +=  '"password": ":1:1525246193:5aR5eYKn6zsQ6vIp"'
request_parameters +=  '}'

# Key derivation functions. See:
# http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python
def sign(key, msg):
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()

def getSignatureKey(key, date_stamp, regionName, serviceName):
    kDate = sign(('AWS4' + key).encode('utf-8'), date_stamp)
    kRegion = sign(kDate, regionName)
    kService = sign(kRegion, serviceName)
    kSigning = sign(kService, 'aws4_request')
    return kSigning

# Read AWS access key from env. variables or configuration file. Best practice is NOT
# to embed credentials in code.
access_key = "ASIAJCV4GTJQPYRAYKYQ"
secret_key = "JJ+BouOzY53lEkJMuQ/w/PtWQaCgo32sEwzD4mw+"
if access_key is None or secret_key is None:
    print 'No access key is available.'
    sys.exit()

# Create a date for headers and the credential string
t = datetime.datetime.utcnow()
amz_date = t.strftime('%Y%m%dT%H%M%SZ')
date_stamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope

amz_date = "20180502T073321Z"
date_stamp = "20180502"

# ************* TASK 1: CREATE A CANONICAL REQUEST *************
# http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

# Step 1 is to define the verb (GET, POST, etc.)--already done.

# Step 2: Create canonical URI--the part of the URI from domain to query 
# string (use '/' if no path)
canonical_uri = '/dev/v1/user/associations/robots/3147C60040239620'

## Step 3: Create the canonical query string. In this example, request
# parameters are passed in the body of the request and the query string
# is blank.
canonical_querystring = ''

# Step 4: Create the canonical headers. Header names must be trimmed
# and lowercase, and sorted in code point order from low to high.
# Note that there is a trailing \n.
amz_content_sha256 = hashlib.sha256(request_parameters).hexdigest()
canonical_headers = 'host:' + host + '\n' + 'x-amz-content-sha256:' + amz_content_sha256 + '\n' + 'x-amz-date:' + amz_date + '\n' + 'x-amz-security-token:' + amz_security_token + '\n'
#SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=3bd5e785b3e5110bb4a53b375013ec6888a8fe51ff3f4fa2b05e17ce4457896b
# Step 5: Create the list of signed headers. This lists the headers
# in the canonical_headers list, delimited with ";" and in alpha order.
# Note: The request can include any headers; canonical_headers and
# signed_headers include those that you want to be included in the
# hash of the request. "Host" and "x-amz-date" are always required.
# For DynamoDB, content-type and x-amz-target are also required.
signed_headers = 'host;x-amz-content-sha256;x-amz-date;x-amz-security-token'

# Step 6: Create payload hash. In this example, the payload (body of
# the request) contains the request parameters.
payload_hash = hashlib.sha256(request_parameters).hexdigest()

# Step 7: Combine elements to create canonical request
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash

# ************* TASK 2: CREATE THE STRING TO SIGN*************
# Match the algorithm to the hashing algorithm you use, either SHA-1 or
# SHA-256 (recommended)
algorithm = 'AWS4-HMAC-SHA256'
credential_scope = date_stamp + '/' + region + '/' + service + '/' + 'aws4_request'
string_to_sign = algorithm + '\n' +  amz_date + '\n' +  credential_scope + '\n' +  hashlib.sha256(canonical_request).hexdigest()


# ************* TASK 3: CALCULATE THE SIGNATURE *************
# Create the signing key using the function defined above.
signing_key = getSignatureKey(secret_key, date_stamp, region, service)

# Sign the string_to_sign using the signing_key
signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()

# ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
# Put the signature information in a header named Authorization.
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' +  'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature

# For DynamoDB, the request can include any headers, but MUST include "host", "x-amz-date",
# "x-amz-target", "content-type", and "Authorization". Except for the authorization
# header, the headers must be included in the canonical_headers and signed_headers values, as
# noted earlier. Order here is not significant.
# # Python note: The 'host' header is added automatically by the Python 'requests' library.
headers = {'X-Amz-Date':amz_date,
            'x-amz-security-token':amz_security_token,
           'Content-Type':content_type,
           'x-amz-content-sha256':amz_content_sha256,
           'Accept': 'application/json; charset=utf-8',
           'Authorization':authorization_header,
           'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 7.0; Nexus 9 Build/NRD90M)'}

# ************* SEND THE REQUEST *************
print '\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++'
#GET请求需要适当修改加入?和参数
#request_url = endpoint + canonical_uri + '?' + canonical_querystring
print 'Request URL = ' + endpoint + canonical_uri
print headers
r = requests.post(endpoint + canonical_uri, data=request_parameters, headers=headers)

print '\nRESPONSE++++++++++++++++++++++++++++++++++++'
print 'Response code: %d\n' % r.status_code
print r.text
 
posted @ 2018-06-05 09:50  ASCII0x03  阅读(2559)  评论(0编辑  收藏  举报