V4 signing process with your own program

Stay organized with collections Save and categorize content based on your preferences.

This page describes an algorithm for implementing the V4 signing process so that you can create Cloud Storage RSA key signed URLs in your own workflow, using a programming language of your choice.


Before creating a program that implements the V4 signing process, you should:

  1. Generate a new private key, or have an existing private key for a service account. The key can be in either JSON or PKCS12 format.

    For more information on private keys and service accounts, see Service Accounts.

  2. Give the service account sufficient permission such that it could perform the request that the signed URL will make.

    For example, if your signed URL will allow a user to download an object, the service account should have storage.objects.get permission on the object.

Algorithm for signing URLs

Your program should include the following steps:

  1. Construct the canonical request as a string. The canonical request defines elements that users must include in their request when they use your signed URL.

    See Canonical Requests for details about the parts and format required.

  2. Construct the string-to-sign. The string-to-sign is the basis for creating a signature and includes within it the hex-encoded hash value of the canonical request.

    See Signatures for details about the format of the string-to-sign.

  3. Sign the string-to-sign using an RSA signature with SHA-256. The result of this signing is your request signature.

    Your programming language should have a library for performing RSA signatures. Alternatively, you can use the IAM signBlob method provided by Google Cloud if your expiration time is 12 hours or less.

  4. Construct the signed URL by using the following concatenation:


    The signed URL has the following components:

    • HOSTNAME: This should be https://storage.googleapis.com.

    • PATH_TO_RESOURCE: This should match the value you used in constructing the canonical request.

    • CANONICAL_QUERY_STRING: This should match the values you used in constructing the canonical request.

    • REQUEST_SIGNATURE: This is the output from using an RSA signature in the previous step.

    Here is a sample completed URL:


Python sample program

You can use the Cloud Storage client libraries to create signed URLs for many common programming languages. See V4 signing process with Cloud Storage tools for examples.

The following sample shows an implementation of the algorithm for signing URLs that does not use the Cloud Storage client libraries. The sample uses the Python programming language but can be adapted to the language of your choice.

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).')

    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(
    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(
    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_request_hash = hashlib.sha256(

    string_to_sign = '\n'.join(['GOOG4-RSA-SHA256',

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

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

    return signed_url

What's next