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

本页面介绍了实现 V4 签名过程的算法,以便您可以使用所选编程语言在自己的工作流程中创建 Cloud Storage RSA 密钥签名网址。签名网址提供有时间限制的读写访问权限,使您可以读取或写入特定 Cloud Storage 资源。只要拥有签名网址,任何人都可以在有效时限内使用该网址,无论是否有 Google 帐号。

如要了解如何使用 Cloud Storage 工具更轻松地创建 Cloud Storage RSA 密钥签名网址,请参阅使用 Cloud Storage 工具的 V4 签名过程。如需详细了解签名网址,请参阅签名网址概览

前提条件

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

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

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

  2. 授予服务帐户足够的权限,以便它可以执行签名网址将发出的请求。

    举例来说,如果签名网址允许用户下载对象,则该服务帐号应该具有该对象的 storage.objects.get 权限。

网址的签名算法

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

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

    规范化请求应具有以下结构,包括在每个元素之间使用换行符:

    HTTP_VERB
    PATH_TO_RESOURCE
    CANONICAL_QUERY_STRING
    CANONICAL_HEADERS
    
    SIGNED_HEADERS
    UNSIGNED-PAYLOAD

    规范化请求具有以下组件:

    • HTTP_VERB:与签名网址搭配使用的 HTTP 动词。

      如需查看各允许值,请参阅 HTTP 动词

    • PATH_TO_RESOURCE通向资源的路径,此路径位于主机名的后面。定义通向资源的路径时,您必须对以下保留字符进行百分号编码:?=!#$&'()*+,:;@[]."

    • CANONICAL_QUERY_STRING:使用签名网址的请求必须包含的查询字符串参数。请按字母顺序添加查询字符串参数,并以 & 分隔各个参数。

    • CANONICAL_HEADERS:使用签名网址的请求必须包含的请求标头的名称:值对。请按字母顺序添加标头,并以 \n 分隔各个标头。

    • SIGNED_HEADERSCANONICAL_HEADERS 的标头名称,以 ; 分隔。

    • UNSIGNED-PAYLOAD:该字符串必须显示在规范化请求的最后一行中。

  2. 使用 SHA-256 哈希函数创建规范化请求的十六进制编码哈希值。

    您的编程语言应具有用于创建 SHA-256 哈希的库。哈希值类似于以下示例所示:

    436b7ce722d03b17d3f790255dd57904f7ed61c02ac5127a0ca8063877e4e42c
  3. 构造要签名的字符串。

    要签名的字符串应具有以下结构,其中包括在每个元素之间使用换行符:

    SIGNING_ALGORITHM
    CURRENT_DATETIME
    CREDENTIAL_SCOPE
    HASHED_CANONICAL_REQUEST

    要签名的字符串包含以下组件:

    • SIGNING_ALGORITHM:这应该是 GOOG4-RSA-SHA256

    • CURRENT_DATETIME:采用 ISO 8601 基本格式 YYYYMMDD'T'HHMMSS'Z' 的当前日期和时间。

    • CREDENTIAL SCOPE:对要签名的字符串进行签名的请求对应的凭据范围

    • HASHED_CANONICAL_REQUEST:您在上一步中创建的规范化请求的十六进制编码 SHA-256 哈希。

  4. 通过采用 SHA-256 的 RSA 签名对要签名的字符串进行签名。签名的结果是您的请求签名。

    您的编程语言应该有一个用于执行 RSA 签名的库。在 Google App Engine 应用中,您可以使用 App Engine App Identity 服务对您的字符串进行签名。

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

    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 示例程序

以下示例显示了网址签名算法的实现。该示例使用 Python 编程语言:

import binascii
import collections
import datetime
import hashlib
import sys

# pip install six
from six.moves.urllib.parse import quote

# pip install google-auth
from google.oauth2 import service_account

def generate_signed_url(service_account_file, bucket_name, object_name,
                        expiration, 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(object_name, safe='')
    canonical_uri = '/{}/{}'.format(bucket_name, escaped_object_name)

    datetime_now = datetime.datetime.utcnow()
    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()
    headers['host'] = 'storage.googleapis.com'

    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

    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])

    signature = binascii.hexlify(
        google_credentials.signer.sign(string_to_sign)
    ).decode()

    host_name = 'https://storage.googleapis.com'
    signed_url = '{}{}?{}&X-Goog-Signature={}'.format(host_name, canonical_uri,
                                                      canonical_query_string,
                                                      signature)
    return signed_url
此页内容是否有用?请给出您的反馈和评价:

发送以下问题的反馈:

此网页
Cloud Storage
需要帮助?请访问我们的支持页面