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