使用您自己的程式進行 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_HEADERS:「CANONICAL_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. 將 RSA 簽名與 SHA-256 搭配使用,簽署欲簽署字串。這個簽署的結果是您的「要求簽名」

    您的程式設計語言應擁有用來執行 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
需要協助嗎?請前往我們的支援網頁