デジタル署名を使用する

API キーを使用してリクエストにデジタル署名する

使用状況によっては、リクエストを認証する際に API キーに加えてデジタル署名が必要になる場合があります。詳細については、Maps Static API のその他の使用量上限をご覧ください。

デジタル署名の仕組み

デジタル署名では、Google Cloud コンソールで取得できる URL 署名シークレットが使用されます。このシークレットは基本的に秘密鍵であり、お客様と Google との間でのみ共有され、お客様のプロジェクトに固有の情報です。

署名プロセスでは、暗号化アルゴリズムを使用して URL と共有シークレットを結合し、一意の署名が生成されます。この一意の署名により、お客様の API キーを使用してリクエストを生成するサイトは、いずれも許可されたサイトであることがわかります。

未署名のリクエストを制限する

API キーが署名付きリクエストのみを受け入れるようにするには、次の操作を行います。

  1. Cloud コンソールで Google Maps Platform の [割り当て] ページに移動します。
  2. プロジェクトのプルダウン リストから、アプリまたはサイトの API キーを作成したときに使用したプロジェクトを選択します。
  3. [API] プルダウン リストから [Maps Static API] を選択します。
  4. [未署名のリクエスト] セクションを開きます。
  5. [割り当て名] テーブルで、編集する割り当て(1 日あたりの署名なしリクエスト数など)の横にある [編集] をクリックします。
  6. [割り当て上限の編集] ペインで、[割り当て上限] を更新します。
  7. [保存] をクリックします。

始める前に

静的地図を含むウェブページの URL を見つけます。この URL がデジタル署名される URL です。

リクエストに署名する

リクエストの署名は、次の手順で構成されます。

ステップ 1: URL 署名シークレットを取得する

プロジェクトの URL 署名シークレットを取得するには:

  1. Cloud コンソールの Google Maps Platform の [認証情報] ページに移動します。
  2. プロジェクト プルダウンを選択し、Maps Static API の API キーを作成したときに使用したプロジェクトを選択します。
  3. [シークレット生成ツール] カードまで下にスクロールします。[現在のシークレット] フィールドには、現在の URL 署名シークレットが含まれています。
  4. このページには、「URL に署名」ウィジェットも提供されています。この機能により、現在の署名シークレットを使用して Maps Static API のリクエストに自動的に署名できます。 [URL に署名] カードにアクセスするには、画面を下にスクロールしてください。

新しい URL 署名シークレットを取得するには、[シークレットを再生成] を選択します。古いシークレットは、新しいシークレットを生成してから 24 時間で期限切れになります。 24 時間を過ぎると、古いシークレットを含むリクエストは機能しなくなります。

ステップ 2: 署名なしのリクエストを作成する

下記の表にリストされていない文字は、URL エンコードを行う必要があります。

有効な URL 文字の概要
セット文字URL での使用法
英数字 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 テキスト文字列、スキームでの使用(http)、ポート(8080)など
未予約 - _ . ~ テキスト文字列
予約済み ! * ' ( ) ; : @ & = + $ , / ? % # [ ] 制御文字やテキスト文字列

テキスト文字列内で渡される場合、予約済みセットのすべての文字についても同様に当てはまります。詳しくは、特殊文字をご覧ください。

未署名のリクエスト URL を作成します。 手順については、以下のデベロッパー向けドキュメントをご覧ください。

key パラメータには、API キーも必ず含めてください。以下に例を示します。

https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&zoom=12&size=400x400&key=YOUR_API_KEY

署名付きリクエストを生成する

ウェブページ上で Maps Static API または Street View Static API のシンプルな画像をホストする場合やトラブルシューティングを行う場合など、1 回限りの使用の場合は、利用可能な「URL に署名」ウィジェットを使用してデジタル署名を自動的に生成できます。

動的に生成されるリクエストでは、サーバーサイドの署名が必要です。サーバーサイドの署名にはいくつかの追加の中間手順が必要です。

どちらの場合も、末尾に signature パラメータが付加されたリクエスト URL になります。以下に例を示します。

https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&zoom=12&size=400x400&key=YOUR_API_KEY
&signature=BASE64_SIGNATURE
「URL に署名」ウィジェットを使用する

API キーでデジタル署名を生成するには、Google Cloud コンソールの [URL に署名] ウィジェットを使用します。

  1. 「URL に署名」ウィジェットを見つけます(方法についてはステップ 1: URL 署名シークレットを取得するをご参照ください)。
  2. [URL] フィールドに、ステップ 2: 未署名のリクエストを作成するで作成した未署名のリクエスト URL を貼り付けます。
  3. デジタル署名された URL が含まれた [署名付き URL] フィールドが表示されます。必ずコピーを作成してください。
サーバーサイドでデジタル署名を生成する

「今すぐ URL に署名」ウィジェットとは異なり、サーバーサイドでデジタル署名を生成する場合は、追加の操作を行う必要があります。

  1. URL のプロトコル スキームとホスト部分を削除し、パスとクエリのみを残します。

  2. /maps/api/staticmap?center=Z%C3%BCrich&zoom=12&size=400x400&key=YOUR_API_KEY
    
  3. 表示される URL 署名シークレットは、URL 用に変更された Base64 でエンコードされます。

    ほとんどの暗号ライブラリでは、鍵が RAW バイト形式になっているため、署名する前に URL 署名シークレットを元の未加工の形式にデコードする作業が必要になります。

  4. HMAC-SHA1 を使用して上記のリクエストに署名します。
  5. ほとんどの暗号ライブラリでは、RAW バイト形式で署名が生成されるため、URL 用に変更された Base64 を使用して生成されたバイナリ署名を、URL 内で渡すことのできるものに変換する必要があります。

  6. Base64 でエンコードされた署名を、signature パラメータの元の署名なしのリクエスト URL に追加します。以下に例を示します。

    https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&zoom=12&size=400x400&key=YOUR_API_KEY
    &signature=BASE64_SIGNATURE

サーバーサイドのコードを使用して URL 署名を実装する方法については、以下の URL 署名のサンプルコードをご覧ください。

URL 署名のサンプルコード

以下のセクションでは、サーバーサイドのコードを使用して URL 署名を実装する方法を説明します。URL 署名シークレットをユーザーに知られないようにするため、URL は常にサーバーサイドで署名してください。

Python

次の例では、標準の Python ライブラリを使用して URL に署名します(サンプルコードをダウンロード)。

#!/usr/bin/python
# -*- coding: utf-8 -*-
""" Signs a URL using a URL signing secret """

import hashlib
import hmac
import base64
import urllib.parse as urlparse


def sign_url(input_url=None, secret=None):
    """ Sign a request URL with a URL signing secret.
      Usage:
      from urlsigner import sign_url
      signed_url = sign_url(input_url=my_url, secret=SECRET)
      Args:
      input_url - The URL to sign
      secret    - Your URL signing secret
      Returns:
      The signed request URL
  """

    if not input_url or not secret:
        raise Exception("Both input_url and secret are required")

    url = urlparse.urlparse(input_url)

    # We only need to sign the path+query part of the string
    url_to_sign = url.path + "?" + url.query

    # Decode the private key into its binary format
    # We need to decode the URL-encoded private key
    decoded_key = base64.urlsafe_b64decode(secret)

    # Create a signature using the private key and the URL-encoded
    # string using HMAC SHA1. This signature will be binary.
    signature = hmac.new(decoded_key, str.encode(url_to_sign), hashlib.sha1)

    # Encode the binary signature into base64 for use within a URL
    encoded_signature = base64.urlsafe_b64encode(signature.digest())

    original_url = url.scheme + "://" + url.netloc + url.path + "?" + url.query

    # Return signed URL
    return original_url + "&signature=" + encoded_signature.decode()


if __name__ == "__main__":
    input_url = input("URL to Sign: ")
    secret = input("URL signing secret: ")
    print("Signed URL: " + sign_url(input_url, secret))

Java

次の例では、JDK 1.8 以降で利用可能な java.util.Base64 クラスを使用します。これより古いバージョンでは、Apache Commons などを使用する必要があります(サンプルコードをダウンロード)。

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;  // JDK 1.8 only - older versions may need to use Apache Commons or similar.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class UrlSigner {

  // Note: Generally, you should store your private key someplace safe
  // and read them into your code

  private static String keyString = "YOUR_PRIVATE_KEY";
  
  // The URL shown in these examples is a static URL which should already
  // be URL-encoded. In practice, you will likely have code
  // which assembles your URL from user or web service input
  // and plugs those values into its parameters.
  private static String urlString = "YOUR_URL_TO_SIGN";

  // This variable stores the binary key, which is computed from the string (Base64) key
  private static byte[] key;
  
  public static void main(String[] args) throws IOException,
    InvalidKeyException, NoSuchAlgorithmException, URISyntaxException {
    
    BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
    
    String inputUrl, inputKey = null;

    // For testing purposes, allow user input for the URL.
    // If no input is entered, use the static URL defined above.    
    System.out.println("Enter the URL (must be URL-encoded) to sign: ");
    inputUrl = input.readLine();
    if (inputUrl.equals("")) {
      inputUrl = urlString;
    }
    
    // Convert the string to a URL so we can parse it
    URL url = new URL(inputUrl);
 
    // For testing purposes, allow user input for the private key.
    // If no input is entered, use the static key defined above.   
    System.out.println("Enter the Private key to sign the URL: ");
    inputKey = input.readLine();
    if (inputKey.equals("")) {
      inputKey = keyString;
    }
    
    UrlSigner signer = new UrlSigner(inputKey);
    String request = signer.signRequest(url.getPath(),url.getQuery());
    
    System.out.println("Signed URL :" + url.getProtocol() + "://" + url.getHost() + request);
  }
  
  public UrlSigner(String keyString) throws IOException {
    // Convert the key from 'web safe' base 64 to binary
    keyString = keyString.replace('-', '+');
    keyString = keyString.replace('_', '/');
    System.out.println("Key: " + keyString);
    // Base64 is JDK 1.8 only - older versions may need to use Apache Commons or similar.
    this.key = Base64.getDecoder().decode(keyString);
  }

  public String signRequest(String path, String query) throws NoSuchAlgorithmException,
    InvalidKeyException, UnsupportedEncodingException, URISyntaxException {
    
    // Retrieve the proper URL components to sign
    String resource = path + '?' + query;
    
    // Get an HMAC-SHA1 signing key from the raw key bytes
    SecretKeySpec sha1Key = new SecretKeySpec(key, "HmacSHA1");

    // Get an HMAC-SHA1 Mac instance and initialize it with the HMAC-SHA1 key
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(sha1Key);

    // compute the binary signature for the request
    byte[] sigBytes = mac.doFinal(resource.getBytes());

    // base 64 encode the binary signature
    // Base64 is JDK 1.8 only - older versions may need to use Apache Commons or similar.
    String signature = Base64.getEncoder().encodeToString(sigBytes);
    
    // convert the signature to 'web safe' base 64
    signature = signature.replace('+', '-');
    signature = signature.replace('/', '_');
    
    return resource + "&signature=" + signature;
  }
}

Node JS

次の例では、ネイティブの Node モジュールを使用して URL に署名します(サンプルコードをダウンロード)。

'use strict'

const crypto = require('crypto');
const url = require('url');

/**
 * Convert from 'web safe' base64 to true base64.
 *
 * @param  {string} safeEncodedString The code you want to translate
 *                                    from a web safe form.
 * @return {string}
 */
function removeWebSafe(safeEncodedString) {
  return safeEncodedString.replace(/-/g, '+').replace(/_/g, '/');
}

/**
 * Convert from true base64 to 'web safe' base64
 *
 * @param  {string} encodedString The code you want to translate to a
 *                                web safe form.
 * @return {string}
 */
function makeWebSafe(encodedString) {
  return encodedString.replace(/\+/g, '-').replace(/\//g, '_');
}

/**
 * Takes a base64 code and decodes it.
 *
 * @param  {string} code The encoded data.
 * @return {string}
 */
function decodeBase64Hash(code) {
  // "new Buffer(...)" is deprecated. Use Buffer.from if it exists.
  return Buffer.from ? Buffer.from(code, 'base64') : new Buffer(code, 'base64');
}

/**
 * Takes a key and signs the data with it.
 *
 * @param  {string} key  Your unique secret key.
 * @param  {string} data The url to sign.
 * @return {string}
 */
function encodeBase64Hash(key, data) {
  return crypto.createHmac('sha1', key).update(data).digest('base64');
}

/**
 * Sign a URL using a secret key.
 *
 * @param  {string} path   The url you want to sign.
 * @param  {string} secret Your unique secret key.
 * @return {string}
 */
function sign(path, secret) {
  const uri = url.parse(path);
  const safeSecret = decodeBase64Hash(removeWebSafe(secret));
  const hashedSignature = makeWebSafe(encodeBase64Hash(safeSecret, uri.path));
  return url.format(uri) + '&signature=' + hashedSignature;
}

C#

次の例では、デフォルトの System.Security.Cryptography ライブラリを使用して URL リクエストに署名します。URL セーフ バージョンを実装するには、デフォルトの Base64 エンコーディングを変換する必要があります(サンプルコードをダウンロード)。

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace SignUrl {

  public struct GoogleSignedUrl {

    public static string Sign(string url, string keyString) {
      ASCIIEncoding encoding = new ASCIIEncoding();

      // converting key to bytes will throw an exception, need to replace '-' and '_' characters first.
      string usablePrivateKey = keyString.Replace("-", "+").Replace("_", "/");
      byte[] privateKeyBytes = Convert.FromBase64String(usablePrivateKey);

      Uri uri = new Uri(url);
      byte[] encodedPathAndQueryBytes = encoding.GetBytes(uri.LocalPath + uri.Query);

      // compute the hash
      HMACSHA1 algorithm = new HMACSHA1(privateKeyBytes);
      byte[] hash = algorithm.ComputeHash(encodedPathAndQueryBytes);

      // convert the bytes to string and make url-safe by replacing '+' and '/' characters
      string signature = Convert.ToBase64String(hash).Replace("+", "-").Replace("/", "_");
            
      // Add the signature to the existing URI.
      return uri.Scheme+"://"+uri.Host+uri.LocalPath + uri.Query +"&signature=" + signature;
    }
  }

  class Program {

    static void Main() {
    
      // Note: Generally, you should store your private key someplace safe
      // and read them into your code

      const string keyString = "YOUR_PRIVATE_KEY";
  
      // The URL shown in these examples is a static URL which should already
      // be URL-encoded. In practice, you will likely have code
      // which assembles your URL from user or web service input
      // and plugs those values into its parameters.
      const  string urlString = "YOUR_URL_TO_SIGN";
      
      string inputUrl = null;
      string inputKey = null;
    
      Console.WriteLine("Enter the URL (must be URL-encoded) to sign: ");
      inputUrl = Console.ReadLine();
      if (inputUrl.Length == 0) {
        inputUrl = urlString;
      }     
    
      Console.WriteLine("Enter the Private key to sign the URL: ");
      inputKey = Console.ReadLine();
      if (inputKey.Length == 0) {
        inputKey = keyString;
      }
      
      Console.WriteLine(GoogleSignedUrl.Sign(inputUrl,inputKey));
    }
  }
}

その他の言語のサンプルコード

その他の言語のサンプルコードは、URL 署名プロジェクトで入手できます。

トラブルシューティング

リクエストに無効な署名が含まれている場合、API から HTTP 403 (Forbidden) エラーが返されます。使用された署名シークレットが、渡された API キーにリンクされていない場合、または署名前に ASCII 以外の入力が URL エンコードされていない場合に、このエラーが発生する可能性が高くなります。

この問題をトラブルシューティングするには、リクエスト URL をコピーし、signature クエリ パラメータを削除した後、下記の手順に沿って、有効な署名を再生成します。

API キーでデジタル署名を生成するには、Google Cloud コンソールの [URL に署名] ウィジェットを使用します。

  1. 「URL に署名」ウィジェットを見つけます(方法についてはステップ 1: URL 署名シークレットを取得するをご参照ください)。
  2. [URL] フィールドに、ステップ 2: 未署名のリクエストを作成するで作成した未署名のリクエスト URL を貼り付けます。
  3. デジタル署名された URL が含まれた [署名付き URL] フィールドが表示されます。必ずコピーを作成してください。