使用自己的程序执行 V4 签名流程

本页面介绍了实现 V4 签名过程的算法,以便您可以使用所选编程语言在自己的工作流程中创建 Cloud Storage RSA 密钥签名网址

前提条件

在创建实现 V4 签名过程的程序之前,您应该:

  1. 生成新私钥,或具有用于服务帐号的现有私钥。密钥可以是 JSON 或 PKCS12 格式。

    如需详细了解私钥和服务帐号,请参阅服务帐号

  2. 向服务帐户授予足够的权限,使其能够执行签名网址将发出的请求。

    例如,如果您的签名网址将允许用户下载对象,则服务帐号应拥有对象的 storage.objects.get 权限。

网址签名算法

您的程序应包含以下步骤:

  1. 规范化请求构造为字符串。规范化请求定义用户在使用您的签名网址时,其请求中必须包含的元素。

    如需详细了解所需的请求内容和格式,请参阅规范化请求

  2. 构造待签名字符串。 待签名字符串是创建签名的基础字符串,其中包含规范化请求的十六进制编码哈希值。

    如需详细了解待签名字符串的格式,请参阅签名

  3. 将 RSA 签名与 SHA-256 搭配使用,对待签名字符串进行签名。签名的结果是您的请求签名。

    您的编程语言应具有一个用于执行 RSA 签名的库。或者,如果您的到期时间为 12 小时或更短,则可以使用 Google Cloud 提供的 IAM signBlob 方法。

  4. 使用以下连接构造签名网址:

    HOSTNAME + PATH_TO_RESOURCE + "?" + CANONICAL_QUERY_STRING + "&X-Goog-Signature=" + REQUEST_SIGNATURE

    签名网址包含以下组成部分:

    • HOSTNAME:这应该为 https://storage.googleapis.com

    • PATH_TO_RESOURCE:这应该与您在构造规范化请求时使用的值相匹配。

    • CANONICAL_QUERY_STRING:这应该与您在构造规范化请求时使用的值相匹配。

    • REQUEST_SIGNATURE:这是上一步中使用 RSA 签名的输出。

    完成后的网址示例如下所示:

    https://storage.googleapis.com/example-bucket/cat.jpeg?X-Goog-Algorithm=GOOG4-
    RSA-SHA256&X-Goog-Credential=example%40example-project.iam.gserviceaccount.com
    %2F20181026%2Fus%2Fstorage%2Fgoog4_request&X-Goog-Date=20181026T211942Z&X-Goog
    -expires=3600&X-Goog-Signedheaders=host&X-Goog-Signature=2d2a6f5055eb004b8690b
    9479883292ae7450cdc15f17d7f99bc49b916f9e7429106ed7e5858ae6b4ab0bbbdb1a8ccc364d
    ad3a0da2caebd30887a70c5b2569d089ceb8afbde3eed4dff5086f0db5483998c175980991fe89
    9fbd2cd8cb813b00165e8d56e0a8aa7b3d7a12ee1baa8400611040f05b50a1a8eab5ba223fe137
    5747748de950ec7a4dc50f8382a6ffd4994ac42498d7daa703d9a414d4475154d0e7edaa92d4f2
    507d92c1f7e8efa7cab64df68b5df48575b9259d8d0bdb5dc752bdf07bd162d98ff2924f2e4a26
    fa6b3cede73ad5333c47d146a21c2ab2d97115986a12c68ff37346d6c2ca83e56b8ec8ad956327
    10b489b75c35697d781c38e

Python 示例程序

您可以使用 Cloud Storage 客户端库为许多常用编程语言创建签名网址。如需查看示例,请参阅使用 Cloud Storage 工具执行 V4 签名流程

以下示例展示了未使用 Cloud Storage 客户端库的网址签名算法实现。该示例使用的是 Python 编程语言,但可以根据您选择的语言进行调整。

import binascii
import collections
import datetime
import hashlib
import sys

# pip install google-auth
from google.oauth2 import service_account
# pip install six
import six
from six.moves.urllib.parse import quote

def generate_signed_url(service_account_file, bucket_name, object_name,
                        subresource=None, expiration=604800, http_method='GET',
                        query_parameters=None, headers=None):

    if expiration > 604800:
        print('Expiration Time can\'t be longer than 604800 seconds (7 days).')
        sys.exit(1)

    escaped_object_name = quote(six.ensure_binary(object_name), safe=b'/~')
    canonical_uri = '/{}'.format(escaped_object_name)

    datetime_now = datetime.datetime.now(tz=datetime.timezone.utc)
    request_timestamp = datetime_now.strftime('%Y%m%dT%H%M%SZ')
    datestamp = datetime_now.strftime('%Y%m%d')

    google_credentials = service_account.Credentials.from_service_account_file(
        service_account_file)
    client_email = google_credentials.service_account_email
    credential_scope = '{}/auto/storage/goog4_request'.format(datestamp)
    credential = '{}/{}'.format(client_email, credential_scope)

    if headers is None:
        headers = dict()
    host = '{}.storage.googleapis.com'.format(bucket_name)
    headers['host'] = host

    canonical_headers = ''
    ordered_headers = collections.OrderedDict(sorted(headers.items()))
    for k, v in ordered_headers.items():
        lower_k = str(k).lower()
        strip_v = str(v).lower()
        canonical_headers += '{}:{}\n'.format(lower_k, strip_v)

    signed_headers = ''
    for k, _ in ordered_headers.items():
        lower_k = str(k).lower()
        signed_headers += '{};'.format(lower_k)
    signed_headers = signed_headers[:-1]  # remove trailing ';'

    if query_parameters is None:
        query_parameters = dict()
    query_parameters['X-Goog-Algorithm'] = 'GOOG4-RSA-SHA256'
    query_parameters['X-Goog-Credential'] = credential
    query_parameters['X-Goog-Date'] = request_timestamp
    query_parameters['X-Goog-Expires'] = expiration
    query_parameters['X-Goog-SignedHeaders'] = signed_headers
    if subresource:
        query_parameters[subresource] = ''

    canonical_query_string = ''
    ordered_query_parameters = collections.OrderedDict(
        sorted(query_parameters.items()))
    for k, v in ordered_query_parameters.items():
        encoded_k = quote(str(k), safe='')
        encoded_v = quote(str(v), safe='')
        canonical_query_string += '{}={}&'.format(encoded_k, encoded_v)
    canonical_query_string = canonical_query_string[:-1]  # remove trailing '&'

    canonical_request = '\n'.join([http_method,
                                   canonical_uri,
                                   canonical_query_string,
                                   canonical_headers,
                                   signed_headers,
                                   'UNSIGNED-PAYLOAD'])

    canonical_request_hash = hashlib.sha256(
        canonical_request.encode()).hexdigest()

    string_to_sign = '\n'.join(['GOOG4-RSA-SHA256',
                                request_timestamp,
                                credential_scope,
                                canonical_request_hash])

    # signer.sign() signs using RSA-SHA256 with PKCS1v15 padding
    signature = binascii.hexlify(
        google_credentials.signer.sign(string_to_sign)
    ).decode()

    scheme_and_host = '{}://{}'.format('https', host)
    signed_url = '{}{}?{}&x-goog-signature={}'.format(
        scheme_and_host, canonical_uri, canonical_query_string, signature)

    return signed_url

后续步骤