使用您自己的程式進行 V4 簽署程序

本頁面說明實作 V4 簽署程序的演算法,讓您能使用偏好的程式設計語言,在自己的工作流程中建立 Cloud Storage RSA 金鑰經簽署的網址。

事前準備

建立實作 V4 簽署程序的程式之前,請先完成下列步驟:

  1. 建立服務帳戶。如果已有服務帳戶,可以略過這個步驟。如要進一步瞭解服務帳戶,請參閱「服務帳戶總覽」。

  2. 授予服務帳戶足夠的權限,讓服務帳戶可以執行經簽署的網址提出的要求。

    舉例來說,如果經簽署的網址允許使用者讀取物件資料,服務帳戶本身必須具備讀取物件資料的權限。

  3. 如果打算使用的簽署演算法未內建於Google Cloud,請產生新的私密金鑰,或使用服務帳戶的現有私密金鑰。金鑰可以採用 JSON 或 PKCS12 格式。

網址簽署演算法

您的程式應包含下列步驟:

  1. 建構「標準化要求」做為字串。標準要求定義了使用者在使用經簽署的網址時,必須在要求中包含的元素。

    如需必要部分和格式的詳細資訊,請參閱標準要求。

  2. 建構待簽署字串。待簽署字串是建立簽章的基礎,其中包含標準要求的十六進位編碼雜湊值。

    如要進一步瞭解待簽署字串的格式,請參閱「簽章」。

  3. 將 RSA 簽名與 SHA-256 搭配使用,簽署欲簽署字串。簽署結果就是要求簽章。簽署方式有以下幾種:

  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
from urllib.parse import quote

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

# pip install six
import six


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 = f"/{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 = f"{datestamp}/auto/storage/goog4_request"
    credential = f"{client_email}/{credential_scope}"

    if headers is None:
        headers = dict()
    host = f"{bucket_name}.storage.googleapis.com"
    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 += f"{lower_k}:{strip_v}\n"

    signed_headers = ""
    for k, _ in ordered_headers.items():
        lower_k = str(k).lower()
        signed_headers += f"{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 += f"{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

後續步驟