Verify response with signature

For a response with a signature, verify the signature with the corresponding Google Pay public key.

  • If the verification succeeds, check that the virtual payment address, transaction ID and amount in the response are correct, and only then proceed based on the response status. Make sure that you use the tezResponse field, but not the deprecated fields to verify the response.
  • If the verification doesn't succeed, we recommend you consider this as a payment failure, and proceed accordingly.
  • For a response without a signature, we recommend that you treat this as a payment failure. Response without a signature might happen. One of the possible reasons is when user doesn't have the internet connectivity while making payment for the selected goods.

How to verify signature in Google Pay response

There are a few things that must be done to verify the signature in a Google Pay response.

  1. Get the public key that corresponds to the public key ID in the response.
  2. Hash the Google Pay response using SHA256 and get the UTF-8 message byte array.
  3. Decode the string signature using the base16 lowercase method to get the signature byte array.
  4. Verify the signature byte array with the message byte array and the public key using SHA256 with the ECDSA algorithm.

The current set of active public keys for signatureKeyId are available in google_pay_public_keys.json. Find the correct keyValue by matching the keyId you received in the response, the keyValue is a Base64 encoded version of the key which you'll see in a normal pem file. The public key will change every two years and you will be notified about the new key.

How to implement SignatureVerifier class

In this sample Java implementation, two open source libraries are used:

  • Bouncy Castle: a collection of APIs used in cryptography.
  • Guava: Google Core Libraries for Java.

The following code snippet illustrates how to implement the SignatureVerifier class.

Java

package com.google.pay;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.util.io.pem.PemReader;

/** Helper class to verify a signature returned in Google Pay response. */
public class SignatureVerifier {

  private static final String CHARSET = "utf-8";
  private static final String ENCRYPTION_ALGORITHM = "EC";
  private static final String HASH_ENCRYPTION_ALGORITHM = "SHA256withECDSA";

  /**
   * Verify the signature for the given signing message using the PEM format public key.
   *
   * @param message the signing message returned in Google Pay response.
   * @param signature the signature returned in Google Pay response.
   * @param publicKeyPemFilePath the path of the PEM format public key file corresponds to the
   *     signature key ID returned in Google Pay response.
   * @return true if the signature verification succeeds, false otherwise.
   */
  public static boolean verifySignature(
      String message, String signature, String publicKeyPemFilePath) {

    String hashMessage = Hashing.sha256().hashString(message, StandardCharsets.UTF_8).toString();
    System.out.printf("Signing message: %s\n", message);
    System.out.printf("Hash of the signing message: %s\n", hashMessage);

    boolean result = false;
    try {
      byte[] hashMessageBytes = hashMessage.getBytes(CHARSET);
      byte[] signatureBytes = BaseEncoding.base16().lowerCase().decode(signature);
      PublicKey publicKey = getPublicKeyFromPemFile(publicKeyPemFilePath);
      result = verify(hashMessageBytes, signatureBytes, publicKey);
    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.printf("Signature verification result: %b\n", result);
    return result;
  }

  /**
   * Verify the signature for the given signing message using the given public key.
   *
   * @param messageBytes the byte array of the signing message.
   * @param signatureBytes the byte array of the signature.
   * @param publicKey the public key.
   * @return true if the signature verification succeeds, false otherwise.
   * @throws NoSuchAlgorithmException if the given algorithm is not available.
   * @throws InvalidKeyException if the given key is invalid.
   * @throws UnsupportedEncodingException if the given encoding is not supported.
   * @throws SignatureException if exception occurs during signature verification .
   */
  private static boolean verify(byte[] messageBytes, byte[] signatureBytes, PublicKey publicKey)
      throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException,
          SignatureException {
    final Signature signature = Signature.getInstance(HASH_ENCRYPTION_ALGORITHM);
    signature.initVerify(publicKey);
    signature.update(messageBytes);
    return signature.verify(signatureBytes);
  }

  /**
   * Read the public key from given public key file.
   *
   * @param publicKeyPemFilePath the path of a PEM format public key file.
   * @return the generated public key.
   * @throws NoSuchAlgorithmException if the given algorithm is not available.
   * @throws IOException if the given file does not exists.
   * @throws InvalidKeySpecException if the key spec is invalid.
   */
  private static PublicKey getPublicKeyFromPemFile(String publicKeyPemFilePath)
      throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
    final KeyFactory keyFactory = KeyFactory.getInstance(ENCRYPTION_ALGORITHM);
    final PemReader reader = new PemReader(new FileReader(publicKeyPemFilePath));
    final byte[] content = reader.readPemObject().getContent();
    final X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(content);
    reader.close();
    return keyFactory.generatePublic(publicKeySpec);
  }
}

C#

The following C# example uses the Bouncy Castle library:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Google.Google Pay.Samples {
    /// 
    /// Helper class to verify the signature returned in Google Pay response
    /// 
    class SignatureVerifier {
        private const int FROM_BASE16 = 16;
        private const string LOWERCASE_HEX_FORMAT = "x2";
        private const string HASH_ENCRYPTION_ALGORITHM = "SHA256withECDSA";

        /// 
        /// Verify  signature for the given message using PEM format public key. 
        /// 
        /// true if the signature verification succeeds, false otherwise.
        /// the signing message returned in Google Pay response.
        /// the signature returned in Google Pay response.
        ///  the path of the PEM format public key file corresponds to the 
        /// signature key ID returned in Google Pay response.
        public static bool verifySignature(string message, string signature, string publicKeyPemFilePath) {
            string hashMessage;
            using (SHA256 sha256 = SHA256.Create()) {
                byte[] data = sha256.ComputeHash(Encoding.Default.GetBytes(message));
                hashMessage = convertByteArrayToHexString(data);
                Console.WriteLine("Signing message: {0}", message);
                Console.WriteLine("Hash of the signing message: {0}", hashMessage);
            }
            bool result = false;
            byte[] hashMessageBytes = Encoding.UTF8.GetBytes(hashMessage);
            byte[] signatureBytes = convertHexStringToByteArray(signature); 
            AsymmetricKeyParameter publicKey = getPublicKeyFromPemFile(publicKeyPemFilePath);
            result = verify(hashMessageBytes, signatureBytes, publicKey);
            return result;
        }

        /// 
        ///  Verify signature for the given message using given public key
        /// 
        /// true if the signature verification succeeds, false otherwise.
        /// the byte array of the signing message.
        /// the byte array of the signature.
        ///  the public key.
        public static bool verify(byte[] messageBytes, byte[] signatureBytes, AsymmetricKeyParameter publicKey) {
            ISigner signer = SignerUtilities.GetSigner(HASH_ENCRYPTION_ALGORITHM);
            signer.Init(false, publicKey);
            signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
            return signer.VerifySignature(signatureBytes);
        }

        /// 
        /// Converts the byte array to hex string.
        /// 
        /// The byte array to hex string.
        /// Data.
        private static string convertByteArrayToHexString(byte[] data) {
            if (data == null) {
                throw new ArgumentNullException(nameof(data));
            }
            if (data.Length == 0) {
                throw new ArgumentException("{0} array cannot be empty", nameof(data));
            }
            StringBuilder sBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++) {
                sBuilder.Append(data[i].ToString(LOWERCASE_HEX_FORMAT));
            }
            return sBuilder.ToString();
        }

        /// 
        /// Converts the hex string to bytes.
        /// 
        /// The string to bytes.
        /// Hex string.
        public static byte[] convertHexStringToByteArray(string hexString) {
            if (hexString == null) {
                throw new ArgumentNullException(nameof(hexString));
            }
            if (hexString.Length % 2 != 0) {
                throw new ArgumentException("{0} must have an even length", nameof(hexString));
            }
            byte[] bytes = new byte[hexString.Length / 2];
            for (int i = 0; i < bytes.Length; i++) {
                string currentHex = hexString.Substring(i * 2, 2);
                bytes[i] = Convert.ToByte(currentHex, FROM_BASE16);
            }
            return bytes;
        }

        /// 
        ///  Read the public key from given pem file.
        /// 
        /// The asymmetric key parameter.
        /// Pem filename.
        private static AsymmetricKeyParameter getPublicKeyFromPemFile(string pemFilename) {
            StreamReader fileStream = System.IO.File.OpenText(pemFilename);
            PemReader pemReader = new PemReader(fileStream);
            AsymmetricKeyParameter keyParameter = (AsymmetricKeyParameter)pemReader.ReadObject();
            return keyParameter;
        }
    }
}

PHP

The following example uses the OpenSSL library to validate the digital signature:

       
       $message = "{\"Status\":\"SUCCESS\",\"amount\":\"10.01\",\"txnRef\":\"test reference      id\",\"toVpa\":\"merchant3@icici\",\"txnId\":\"ICI6a88c3ae581649f7b0e2157504358ead\",\"responseCode\":\"0\"}";

    $publicKey = file_get_contents("/var/www/html/php/googlepayPublicKeyV1.pem"); // a valid path to public key

    $signature = "30450221008f32b3ac01a00e5b3c1f53e71e6f111546b60a9f6911df2ad75af5921f8681d002203b090e25a1b6dc7132530116d03024f8ab25795931f34a1724bed8f7389909cc";

    $alg = OPENSSL_ALGO_SHA256;

    // verify using openssl library
    $success = openssl_verify(hash("sha256", $message), hex2bin($signature), $publicKey, $alg);

    if ($success === -1) {
    echo "openssl_verify() failed with error. " . openssl_error_string() . "\n";
    } elseif ($success === 1) {
    echo "signature verification was successful\n";
    } else {
    echo "signature verification failed. \n";
    }

    ?>
  

How to use the signature verifier class

The following examples illustrate how to use the signatureVerifier class in Java and C# implemtation.

Java

  public static void main(String[] args) {
  String signingMessage =
      "{\"Status\":\"SUCCESS\",\"amount\":\"10.01\",\"txnRef\":\"test reference id\",\"toVpa\":\"merchant3@icici\",\"txnId\":\"ICI6a88c3ae581649f7b0e2157504358ead\",\"responseCode\":\"0\"}";
  String signature =     "30450221008f32b3ac01a00e5b3c1f53e71e6f111546b60a9f6911df2ad75af5921f8681d002203b090e25a1b6dc7132530116d03024f8ab25795931f34a1724bed8f7389909cc";
  String publicKeyPath = "./googlepayPublicKeyV1.pem";
  SignatureVerifier.verifySignature(signingMessage, signature, publicKeyPath);
}

C#


static void Main(string[] args) {
    string signingMessage = "{\"Status\":\"SUCCESS\",\"amount\":\"10.01\",\"txnRef\":\"test reference id\"," +
        "\"toVpa\":\"merchant3@icici\",\"txnId\":\"ICI6a88c3ae581649f7b0e2157504358ead\",\"responseCode\":\"0\"}";
    string signature = "30450221008f32b3ac01a00e5b3c1f53e71e6f111546b60a9f6911df" +
        "2ad75af5921f8681d002203b090e25a1b6dc7132530116d03024f8ab25795931f34a1724bed8f7389909cc";
    string publicKeyPath = "./googlepayPublicKeyV1.pem";
    SignatureVerifier.verifySignature(signingMessage, signature, publicKeyPath);
}