Python 计算AWS4签名,Header鉴权与URL鉴权

AWS4 版本签名计算参考

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#  @Time: 2021/7/24 8:12
#  @Author:zhangmingda
#  @File: api_for_aws4_signature.py
#  @Software: PyCharm
#  Description:

from urllib.request import quote
import hashlib
import hmac
import datetime
import requests
import json
import base64

class KscClient(object):
    def __init__(self, ak, sk,service, domain, region, use_ssl=False):
        self.ak = ak
        self.sk = sk
        self.host = service + "." + domain
        self.region = region
        self.service = service
        self.protocol = 'https' if use_ssl else 'http'
        self.base_url = self.protocol + '://' + self.host + '/'

    # 创建规范化请求查询字符串方法
    def generate_canonical_querystring(self, **params):
        '''
        :param params: 查询字符串字典
        :return: url编码后的查询字符串key1=value1&key2=value2 ...
        '''
        sorted_params = ''
        sorted_param_keys = sorted(params.keys())
        for param in sorted_param_keys:
            # print(param)
            sorted_params += quote(param) + '=' + quote(params.get(param), safe='') + '&'  # 查询字符串中的值任何字符串都进行URL编码
            # print(sorted_params)
        return sorted_params[:-1]

    #  生成标准化请求头,请求头大写转小写,排序返回字符串
    def generate_canonical_headers(self, **headers):
        '''
        :param headers: 所有用来签名的请求头
        :return: 返回小写请求头排序后的key:value 字符串,每个key:value 之间\n换行
        '''
        sorted_headers = ''                                         # 准备标准请求头key+value字符串
        sorted_signed_headers = ''                                  # 准备签名的请求头key组合字符串
        header_keys = headers.keys()                                # 获取请求头所有key
        lower_headers = {}                                          # 准备存放小写请求头和值的字典
        for header in header_keys:                                  # 把大写请求头字典都转换为小写请求头字典
            lower_headers[header.lower()] = headers.get(header)
        sorted_lower_header_keys = sorted(lower_headers.keys())     # 把签名的请求头排序

        '''排序后的请求头和值进行组合字符串'''
        for lower_header in sorted_lower_header_keys:
            sorted_headers += lower_header + ':' + str(lower_headers[lower_header]).strip() + '\n'
            sorted_signed_headers += lower_header + ";"
        # print("============请求头排序==============")
        # print(sorted_headers)
        # print('------被签名的请求头----')
        # print(sorted_signed_headers)
        # print("==========请求头排序end============")
        sorted_lower_headers = {
            'sorted_headers': sorted_headers,                           # 签名的请求头key 和 value
            'signed_headers': sorted_signed_headers[:-1]                # 被签名的请求头字符串组合,最后一个分号; 不要
        }
        return sorted_lower_headers

    # 十六进制请求体Sha256Hash值--->请求体用
    def bytes_data_sha256_hex(self, bytes_data=''.encode()):
        '''
        :param binary_data: 字节码文件
        :return: 返回请求体内容的sha256 哈希值
        '''
        sha256 = hashlib.sha256()
        sha256.update(bytes_data)
        return sha256.hexdigest().lower()

    # 构建被签名字符串,把标准请求canonical_request 哈希取16进制用
    def str_sha256_hex(self, string=''):
        '''
        :param string:
        :return: 字符串的sha256哈希值
        '''
        sha256 = hashlib.sha256()
        sha256.update(string.encode())
        return sha256.hexdigest()

    # UTC时间字符串工具
    def get_utc_datetime(self):
        now_utc = datetime.datetime.utcnow()
        datetime_utc = now_utc.strftime('%Y%m%dT%H%M%SZ')
        date_utc = now_utc.strftime('%Y%m%d')
        return datetime_utc, date_utc

    # 生成规范化请求的字符串方法
    def generate_canonical_request(self, method, encode_uri, canonical_querystring, canonical_headers, signed_headers,payload_sha256_hex):
        ''' 1.1 规范化请求CanonicalRequest计算方法'''
        return method + '\n' \
               + encode_uri + '\n' \
               + canonical_querystring + '\n' \
               + canonical_headers + '\n' \
               + signed_headers + '\n' \
               + payload_sha256_hex  # 请求体不参与签名 时 : + 'UNSIGNED-PAYLOAD'

    # 生成被签名字符串的方法
    def generate_string_tosign(self, algorithm, datetime_utc, credentialscope, canonical_reques_sha256_hex ):
        ''' 1.2 被签名字符串计算方法'''
        return algorithm + '\n' \
                        + datetime_utc + '\n' \
                        + credentialscope + '\n' \
                        + canonical_reques_sha256_hex

    # 第二步构建签名key的工具方法
    def encode_string_to_hmac_256_digest(self, encode_salt, msg, digestmod=hashlib.sha256):
        '''
        :param encode_salt: 盐
        :param msg: 字符串信息
        :param digestmod: 摘要算法
        :return: HMAC-SHA256 加盐哈希后的字节
        '''
        digest_maker = hmac.new(encode_salt, msg.encode(), digestmod=digestmod)
        return digest_maker.digest()

    # 创建签名KEY的方法
    def generate_signing_key(self, signature_version,sk, date_utc, region, service, termchar='aws4_request'):
        k_secret = signature_version + sk
        k_date = self.encode_string_to_hmac_256_digest(k_secret.encode(), date_utc)
        k_region = self.encode_string_to_hmac_256_digest(k_date, region)
        k_service = self.encode_string_to_hmac_256_digest(k_region, service)
        k_signing = self.encode_string_to_hmac_256_digest(k_service, termchar)
        return k_signing

    # 创建签名的方法
    def generate_signature(self, signing_key, string_tosign):
        digest_maker = hmac.new(signing_key, string_tosign.encode(), digestmod=hashlib.sha256)
        hex_signature = digest_maker.hexdigest()
        # print('hex_signature:', digest_maker.hexdigest())
        return hex_signature

    # 构建带签名的请求头,或签名url查询字符串
    def get_auth_data(self, method, query_params, append_headers={}, binary_payload=''.encode()):
        # ========================================1. 创建被签名字符串======================================
        # 1.1 构建标准化请求
        method = method             # 1.1.1 HTTP方法
        canonical_uri = '/'         # 1.1.2 资源URI
        encode_uri = quote(canonical_uri, safe="/")  # 1.1.2 将URI编码成%字符串格式 不包含? 后的url查询参数
        payload_sha256_hex = self.bytes_data_sha256_hex(binary_payload)  # 1.1.5 请求体sha256 十六进制HASH值
        datetime_utc, date_utc = self.get_utc_datetime()
        # 准备请求头
        signature_headers = {
            'Host': self.host,
            'X-amz-date': datetime_utc
        }
        signature_headers.update(append_headers)
        # 请求头排序格式化
        sorted_lower_headers = self.generate_canonical_headers(**signature_headers)
        canonical_headers = sorted_lower_headers.get('sorted_headers')      # 1.1.4 小写排序后的请求头key:value
        signed_headers = sorted_lower_headers.get('signed_headers')         # 1.1.5 小写排序后的签名头
        # 1.2 创建信任状
        credentialscope = date_utc + "/" + self.region + "/" + self.service + "/aws4_request"
        # 查询参数字典
        auth_params = {}
        # 签名放在URL中时计算签名传递参数
        if query_params:
            auth_params = {
                'X-Amz-Algorithm': "AWS4-HMAC-SHA256",
                'X-Amz-Credential': self.ak + '/' + credentialscope,
                'X-Amz-Date': datetime_utc,
                'X-Amz-SignedHeaders': signed_headers,
            }
            auth_params.update(query_params)
            # print('auth_params: ', auth_params)
        auth_in_header_canonical_querystring = self.generate_canonical_querystring(**query_params)      # 1.1.3 通过header传递签名的查询字符串
        auth_in_queryparam_canonical_querystring = self.generate_canonical_querystring(**auth_params)   # 1.1.3 通过URL传递签名的查询字符串
        canonical_request_auth_in_header = self.generate_canonical_request(method, encode_uri, auth_in_header_canonical_querystring, canonical_headers, signed_headers, payload_sha256_hex)
        canonical_request_auth_in_url = self.generate_canonical_request(method, encode_uri, auth_in_queryparam_canonical_querystring, canonical_headers, signed_headers, payload_sha256_hex)
        print("Header传递签名的规范化请求".center(50, "="))
        print(canonical_request_auth_in_header)
        print("canonical_request_auth_in_url".center(50, "*"))
        print(canonical_request_auth_in_header)
        print("规范化化请求done".center(50, "="))

        # 1.2 创建被签名字的符串StringToSign
        canonical_request_auth_in_header_sha256_hex = self.str_sha256_hex(canonical_request_auth_in_header)  # 规范化请求的256哈希值
        canonical_request_auth_in_url_sha256_hex = self.str_sha256_hex(canonical_request_auth_in_url)        # 规范化请求的256哈希值
        algorithm = 'AWS4-HMAC-SHA256'
        string_tosign_auth_in_header = self.generate_string_tosign(algorithm, datetime_utc, credentialscope, canonical_request_auth_in_header_sha256_hex)
        string_tosign_auth_in_url = self.generate_string_tosign(algorithm, datetime_utc, credentialscope, canonical_request_auth_in_url_sha256_hex)

        print('Header传递签名被签名的字符串'.center(50, '='))
        print(string_tosign_auth_in_header)
        print('URL传递签名被签名的字符串'.center(50, '*'))
        print(string_tosign_auth_in_url)
        print('被签名的字符串Done!'.center(50,'='))

        # ========================================2. 创建签名 ======================================
        # 2.1创建签名签名秘钥
        signature_version = 'AWS4'
        signing_key = self.generate_signing_key(signature_version, self.sk, date_utc, self.region, self.service)
        print("签名秘钥:",signing_key)
        # 2.2 创建签名
        signature_for_header = self.generate_signature(signing_key, string_tosign_auth_in_header)
        signature_for_url = self.generate_signature(signing_key, string_tosign_auth_in_url)
        print('Header鉴权的签名:', signature_for_header)
        print('url鉴权的签名:', signature_for_url)
        # ========================================3 请求头或URL 传递签名鉴权和参数 ======================================
        authorization_headers = {
            'Authorization': "AWS4-HMAC-SHA256 Credential="
                             + self.ak + '/'
                             + credentialscope + ", "
                             + "SignedHeaders="
                             + signed_headers + ', '
                             + 'Signature=' + signature_for_header,
        }
        # 3.1请求头携带签名,返回带签名的请求头
        authorization_headers.update(signature_headers)

        # 3.2 url携带签名返回带签名的URL查询字符串
        request_query_params = {
            'X-Amz-Signature': signature_for_url
        }
        request_query_params.update(auth_params)
        authorization_params = self.generate_canonical_querystring(**request_query_params)
        # print('request_query_params: ', request_query_params)
        # print('authorization_params:',authorization_params)
        auth_data = {
            'authorization_headers': authorization_headers,
            'authorization_params': request_query_params,
            'signature_headers': signature_headers,  # get object 在URL里面传递签名 请求头要带的签名头
        }
        return auth_data

    #  通过标准请求头方式传递签名GET文件
    def get_test_auth_in_header(self, query_params={}, append_headers={}):
        method = 'GET'                                      # 1.1.1 HTTP方法
        auth_data = self.get_auth_data(method, query_params, append_headers)
        authorization_headers = auth_data.get('authorization_headers')
        req = requests.get(url=self.base_url, headers=authorization_headers, params=query_params)
        print('Header鉴权的URL:', req.url)
        print(req.status_code)
        print(req.text)

    def get_test_auth_in_query_param(self,query_params={}, append_headers={}):
        method = 'GET'  # 1.1.1 HTTP方法
        auth_data = self.get_auth_data(method, query_params, append_headers)
        auth_query_params = auth_data.get('authorization_params')
        signature_headers = auth_data.get('signature_headers')
        canonical_querystring = self.generate_canonical_querystring(**auth_query_params)

        # 通过URL传递鉴权=requests 传参方式1
        url_for_auth = self.base_url + "?" + canonical_querystring
        req2 = requests.get(url=url_for_auth, headers=signature_headers)
        print("手工拼接鉴权的URL:", url_for_auth)
        print(req2.status_code)
        print(req2.text)
        # 通过URL传递鉴权=requests 传参方式2
        req3 = requests.get(self.base_url, headers=signature_headers, params=auth_query_params)
        # req3 = requests.get(url=self.base_url, headers=signature_headers, params=canonical_querystring)
        print("requests自动拼接鉴权的URL:", req3.url)
        print(req3.status_code)
        print(req3.text)

if __name__ == '__main__':
    ak = 'XXXXXXXXXXXXXXX'
    sk = 'XXXXXXXXXXXXXXXXXXXXXXXXX'
    service = 'kcm'
    domain = 'api.ksyun.com'
    region = 'cn-beijing-6'
    ksc = KscClient(ak, sk, service, domain, region)
    query_params = {
        'Action': 'GetDownloadLink',
        'Version': '2016-03-04',
        'CertificateId': 'kcm2021022216204501'
    }
    ksc.get_test_auth_in_header(query_params=query_params)
    # ksc.get_test_auth_in_query_param(query_params=query_params)

Header 传递鉴权测试

 

 URL传递鉴权测试

 

posted on 2021-07-24 10:34  zhangmingda  阅读(577)  评论(1编辑  收藏  举报

导航