签名机制

最近更新时间:2018-12-20 17:39:39

签名机制

发送给云数据库MySQL服务的HTTP请求中,必须包含授权参数和其他公共参数。云数据库MySQL服务使用用户的Access Key ID和Secret Access Key进行加密方式来验证请求者身份。Access Key ID和Secret Access Key由金山云发给用户,Access Key ID作为用户的身份标识,Secret Access Key作为用户和服务器短进行签名计算的秘钥。

签名方法

Http请求header中Authorization字段是服务的授权参数,其格式为:

Authorization="[HashMethod][空格]Credential=[access_key]/[scope],SignedHeaders=[signed_headers],Signature=[signature]"

其中:

[HashMethod] ="KSC4-HMAC-SHA256"
[access_key] =用户Access key ID
[scope] = [timestamp]/[region]/[service]/[req_type]

timestamp为yyyyMMdd格式的时间戳,region为请求服务所在区域名,service为访问的服务名,req_type为请求的类型。

[signed_headers]:将Headers按照name升序排列
[signed_headers] = [header_name_1];[header_name_2]....

签名算法: [signature]= sha256(sha256(sha256(sha256(sha256("KSC4"+sign_key,timestamp),region),service),req_type),string_to_sign)

其中:

 [sign_key] = 用户Secret Access Key
 [stringToSign] = "KSC4-HMAC-SHA256" + "\n" + [X-Ksc-Date] + "\n" +[scope] + “\n” + SHA-256([canonical_request])
 [canonical_request] = [HTTPRequestMethod] + "\n" + [CanonicalURI] + "\n" + [CanonicalQueryString] + "\n" + [CanonicalHeaders] + "\n" + [signed_headers] + "\n" + SHA-256([request_body])
 [HTTPRequestMethod] = POST或GET
 [CanonicalURI] = 请求URL中除去Endpoint之外的剩余部分。目前URL等于Endpoint,所以CanonicalURI为空
 [CanonicalQueryString] = 空
 [CanonicalHeaders]:按照[signed_headers]中的排序方式进行排序
 [CanonicalHeaders] =
 LowerCase (HeaderName1) + ‘:’ + Trim (HeaderValue1) + "\n"+LowerCase (HeaderName2) + ‘:’ + Trim (HeaderValue2) + "\n"+.......
 [request_body] = Post 请求的body部分

Python Client Demo

import hashlib
import hmac
import urllib

import datetime
import requests

class RequestComposer:
    def __init__(self, ak, sk, service, request_parameters, method, content_hash=False):
        self.ak = ak
        self.sk = sk
        self.service = service
        self.content_hash = content_hash
        self.header_name_authorization = 'Authorization'
        self.header_name_host = 'Host'
        self.header_name_content_sha256 = 'X-Amz-Content-Sha256'
        self.header_name_date = 'X-Amz-Date'
        self.hash_keyword = 'AWS4'
        self.hash_method = 'AWS4-HMAC-SHA256'
        self.request_parameters = request_parameters
        self.method = method

    @staticmethod
    def get_canonical_headers(headers):
        canonical = []

        for header in headers:
            c_name = header.lower().strip()
            raw_value = str(headers[header])
            if '"' in raw_value:
                c_value = raw_value.strip()
            else:
                c_value = ' '.join(raw_value.strip().split())
            canonical.append('%s:%s' % (c_name, c_value))
        return '\n'.join(sorted(canonical)) + '\n'

    @staticmethod
    def get_signed_headers(headers):
        l = ['%s' % n.lower().strip() for n in headers]
        l = sorted(l)
        return ';'.join(l)

    def get_headers(self, host, region, payload, additional_signing_headers=None):
        if additional_signing_headers is None:
            additional_signing_headers = {}
        if self.ak is None or self.sk is None:
            return None

        # 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

        # ************* 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.
        method = self.method

        # Step 2: Create canonical URI--the part of the URI from domain to query
        # string (use '/' if no path)
        canonical_uri = '/'

        # 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 = self.request_parameters

        # Step 4: Create the canonical headers. Header names and values
        # must be trimmed and lowercase, and sorted in ASCII order.
        # Note that there is a trailing \n.
        headers_to_sign = {
            self.header_name_host: host,  # sign indispensable
            self.header_name_date: amz_date,  # sign indispensable
        }

        if self.content_hash:
            headers_to_sign[self.header_name_content_sha256] = hashlib.sha256(payload).hexdigest()
        # additional headers can be added to signing process
        for h in additional_signing_headers:
            if h not in headers_to_sign:
                headers_to_sign[h] = additional_signing_headers[h]

        canonical_headers = self.get_canonical_headers(headers_to_sign)

        # 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 = self.get_signed_headers(headers_to_sign)

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

        # Step 7: Combine elements to create 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 = self.hash_method
        credential_scope = date_stamp + '/' + region + '/' + self.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 = self.getSignatureKey(self.sk, date_stamp, region, self.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=' + self.ak + '/' + 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 = {}
        for h in headers_to_sign:
            headers[h] = headers_to_sign[h]
            headers[self.header_name_authorization] = authorization_header

        return headers

    # Key derivation functions. See:
    @staticmethod
    def sign(key, msg):
        return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()

    def getSignatureKey(self, key, date_stamp, region_name, service_name):
        kDate = self.sign((self.hash_keyword + key).encode('utf-8'), date_stamp)
        kRegion = self.sign(kDate, region_name)
        kService = self.sign(kRegion, service_name)
        kSigning = self.sign(kService, 'aws4_request')
        return kSigning

class Client(object):
    def __init__(self, host, ak=None, sk=None, service=None, version=None, region=None):
        self.service = service
        self.host = host
        self.ak = ak
        self.sk = sk
        self.region = region
        self.version = version

    def call(self, target, dict_request_parameters=None, headers=None):
        # usiing kop authentication
        if headers is None:
            headers = {}
        url = 'http://%s?' % self.host
        if isinstance(dict_request_parameters, str):
            dict_request_parameters = eval(dict_request_parameters)
        dict_request_parameters['Version'] = self.version

        request_parameters = "Action=%s&" % target + '&'.join(
            ["%s=%s" % (k, urllib.quote(str(dict_request_parameters[k]), ''))
             for k in sorted(dict_request_parameters.keys())])
        url += request_parameters

        composer = RequestComposer(ak=self.ak, sk=self.sk, service=self.service,
                                   request_parameters=request_parameters, method='GET')
        headers = composer.get_headers(host=self.host, region=self.region,
                                       payload="", additional_signing_headers=headers)
        r = requests.get(url, headers=headers)
        return r

AK = 'YOUR ACCESS KEY'
SK = 'YOUR SECRET KEY'
VERSION = "2016-07-01"
SERVICE = "krds"
REGION = "cn-beijing-6"
HOST = "krds.%s.api.ksyun.com" % REGION

client = Client(HOST, AK, SK, SERVICE, VERSION, REGION)
additional_headers = {'Accept': 'application/json'}
result = client.call("DescribeDBEngineVersions", {'Engine': 'MySQL', 'EngineVersion': '5.5'}, additional_headers)
print result.json()

金山云,开启您的云计算之旅

注册有礼