Processo de assinatura V4 com seu próprio programa

Nesta página, você verá um algoritmo para implementar o processo de assinatura da V4, para que você possa criar URLs assinados com chave RSA do Cloud Storage no fluxo de trabalho usando uma linguagem de programação de sua escolha.

Antes de começar

Antes de criar um programa que implemente o processo de assinatura V4, conclua as seguintes etapas:

  1. Crie uma conta de serviço. Se você já tiver uma conta de serviço, pule essa etapa.

  2. Conceda à conta de serviço permissão suficiente para que ela possa executar a solicitação que o URL assinado fará.

    Por exemplo, se o URL assinado permitir que um usuário leia os dados do objeto, a conta de serviço precisará ter permissão para ler os dados do objeto.

  3. Se o algoritmo de assinatura que você pretende usar não estiver integrado ao Google Cloud, gere uma nova chave privada ou tenha uma chave privada existente para a conta de serviço. A chave pode estar no formato JSON ou PKCS12.

Algoritmo para assinatura de URLs

Seu programa precisa incluir os seguintes passos:

  1. Construa o pedido canônico como uma string. A solicitação canônica define elementos que os usuários precisam incluir na solicitação quando usam seu URL assinado.

    Consulte Solicitações canônicas para detalhes sobre as partes e o formato necessários.

  2. Construa a string a ser assinada: A string a ser assinada é a base para criar uma assinatura e inclui nela o valor de hash codificado em hexadecimal da solicitação canônica.

    Consulte Assinaturas para obter detalhes sobre o formato da string a ser assinada.

  3. Assine a string usando uma assinatura RSA com SHA-256. O resultado dessa assinatura é sua assinatura de solicitação. Há várias opções para assinar:

    • É possível usar o método do IAM signBlob fornecido pelo Google Cloud.

    • É possível usar uma linguagem de programação que tenha uma biblioteca para executar assinaturas RSA.

  4. Construa o URL assinado usando a seguinte concatenação:

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

    O URL assinado tem os seguintes componentes:

    • HOSTNAME: deve ser https://storage.googleapis.com.

    • PATH_TO_RESOURCE: precisa corresponder ao valor usado na construção da solicitação canônica.

    • CANONICAL_QUERY_STRING: precisa corresponder aos valores usados na construção da solicitação canônica.

    • REQUEST_SIGNATURE: é a saída do uso de uma assinatura RSA na etapa anterior.

    Veja um exemplo de URL completo:

    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

Programa de amostra em Python

É possível usar as bibliotecas de cliente do Cloud Storage para criar URLs assinados para muitas linguagens de programação comuns. Consulte Processo de assinatura V4 com ferramentas do Cloud Storage para ver exemplos.

O exemplo a seguir mostra uma implementação do algoritmo para assinar URLs que não usam as bibliotecas de cliente do Cloud Storage. A amostra usa a linguagem de programação Python, mas pode ser adaptada à linguagem de sua escolha.

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

A seguir