How to Calculate the Signature


Creating the string to sign

To create a GetPublicKeyId signature, you use the information from the query parameters. All parameters must be in natural-byte order when calculating the signature. The string strictly consists of:

  • The HTTP action - This value is GET.
  • The domain name of the request - For a list of endpoints for each Amazon marketplace, see the Endpoints section in this guide. After the endpoint is a forward slash (/), which separates the endpoint from the parameters.
  • AWSAccessKeyId — Your Amazon MWS account is identified by your access key Id, which Amazon MWS uses to look up your Secret Access Key.
  • Action — The action you want to perform on the endpoint, in this case, the operation GetPublicKeyId.
  • SellerId — Your merchant identifier. We use MerchantId in the request query parameter, but use SellerId while creating the string to sign. MerchantId and SellerId hold the same value.
  • SignatureMethod — The HMAC hash algorithm you are using to calculate your signature. Both HmacSHA256 and HmacSHA1 are supported hash algorithms, but Amazon recommends using HmacSHA256.
  • SignatureVersion — Which signature version is being used. This is Amazon MWS-specific information that tells Amazon MWS the algorithm you used to form the string that is the basis of the signature. For Amazon MWS, this value is currently SignatureVersion=2.
  • Timestamp — Each request must contain the timestamp of the request. The timestamp (or expiration time) you use in an Amazon Marketplace Web Service (Amazon MWS) request must be a dateTime object. A best practice is to provide the timestamp in Coordinated Universal Time (UTC) in ISO 8601 date time format, such as "2009-03-03T18:12:22Z" or "2009-02-23T18:12:22.093-07". The Timestamp attribute must contain the client's machine time in ISO 8601 date time format; requests with a timestamp significantly different than the receiving machine's clock will be rejected.

To create the string to be signed, do the following:

  1. Sort the UTF-8 query string components listed above by parameter name with natural byte ordering. The parameters can come from the GET URI. Note:
    1. We use MerchantId in the request query parameter, but use SellerId while creating the string to sign. MerchantId and SellerId hold the same value.
    2. We do not use the Public Key while creating the string to sign.
  2. URL encode the parameter name and values according to the following rules:
    1. Do not URL encode any of the unreserved characters that RFC 3986 defines. These unreserved characters are A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ).
    2. Percent encode all other characters with %XY, where X and Y are hex characters 0-9 and uppercase A-F.
    3. Percent encode extended UTF-8 characters in the form %XY%ZA….
    4. Percent encode the space character as %20. Do not percent encode the space character as +, as some common encoding schemes do.
  3. Separate the encoded parameter names from their encoded values with the equals sign ( = ) (ASCII character 61), even if the parameter value is empty.
  4. Separate the name-value pairs with an ampersand ( & ) (ASCII code 38).
  5. Create the string to sign according to the following pseudo-grammar (the "\n" represents an ASCII newline).
StringToSign = HTTPVerb + "\n" +
  ValueOfDomainInLowercase + "\n" +
  HTTPRequestURI + "\n" +
  StringToSign <from the preceding step>

The HTTPRequestURI component is the HTTP absolute path component of the URI up to, but not including, the query string.

The following example shows a query string for a GetPublicKeyId request. Note that there are no spaces or line breaks in the sorted parameter string.

GET
pay-api.amazon.com
/live/v2/publicKeyId
AWSAccessKeyId=0PExampleR2&Action=GetPublicKeyId&SellerId=A1ExampleE6&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-02-04T17%3A44%3A33.500Z

This is the string that you sign. The steps that show how to sign the query request string are in the section "Signing the query request."


Endpoints


Region Endpoint
NA pay-api.amazon.com
EU pay-api.amazon.eu
JP pay-api.amazon.jp

Signing a Query Request


The request signature is part of the authentication process for identifying and verifying who is sending a request. It is used as the value for the Signature parameter in the request URL you construct. GetPublicKeyId API verifies both the identity of the sender and whether the sender is registered to use Amazon MWS. Authentication is performed using your AWS access key Id to locate your Secret Key, which you use to create the request signature. If verification fails, the request is not processed.

  1. Create a query request as provided in the sample request in the integration guide. See a sample query request here.
  2. Calculate an RFC 2104-compliant HMAC with the string you just created, using your Secret Key as the key. Both HmacSHA256 and HmacSHA1 are supported hash algorithms, but Amazon recommends using HmacSHA256. Note: Standard port numbers should not be included in the query request string used to calculate the signature. See the Signature Version 2 Signing Process for further information.
  3. Convert the resulting value to base64.
  4. Use the resulting value as the value of the Signature request parameter.

The following example shows how to calculate the signature using Java:

Example Code

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

public class SignatureExample {
    private static final String CHARACTER_ENCODING = "UTF-8";
    final static String ALGORITHM = "HmacSHA256";

    public static void main(String[] args) throws Exception {

        //Use your AWS Access Key ID
        String awsAccessKeyId = "Enter your AWS Access Key Id";

        // Change this secret key to yours
        String secretKey = "Enter your secret key here";

        // Use the endpoint for your marketplace
        String serviceUrl = "https://pay-api.amazon.com";

        //Change this seller Id to yours
        String sellerId = "Enter your seller identifier";

        String resourcePath = "/live/v2/publicKeyId";

        // Create set of parameters needed and store in a map
        HashMap<String, String> parameters = new HashMap<String,String>();

        // Add required parameters. Change these as needed.
        parameters.put("SellerId", urlEncode(sellerId));
        parameters.put("AWSAccessKeyId", urlEncode(awsAccessKeyId));
        parameters.put("Action", urlEncode("GetPublicKeyId"));
        parameters.put("SignatureMethod", urlEncode(ALGORITHM));
        parameters.put("SignatureVersion", urlEncode("2"));
        parameters.put("Timestamp", urlEncode(Constants.CURRENT_DATE));

        // Format the parameters as they will appear in final format
        // (without the signature parameter)
        String stringToSignV2 = calculateStringToSignV2(parameters, serviceUrl, resourcePath);

        System.out.println("\n--- The StringToSign ---\n" + stringToSignV2);
        String signature = sign(stringToSignV2, secretKey);
        System.out.println("\n--- The Signature request parameter ---\n" + urlEncode(signature));
        printCURL(serviceUrl, resourcePath, parameters, signature);

    }

    /* If Signature Version is 2, string to sign is based on following:
     *
     *    1. The HTTP Request Method followed by an ASCII newline (%0A)
     *
     *    2. The HTTP Host header in the form of lowercase host,
     *       followed by an ASCII newline.
     *
     *    3. The URL encoded HTTP absolute path component of the URI
     *       (up to but not including the query string parameters);
     *
     *    4. The concatenation of all query string components (names and
     *       values) as UTF-8 characters which are URL encoded as per RFC
     *       3986 (hex characters MUST be uppercase), sorted using
     *       lexicographic byte ordering. Parameter names are separated from
     *       their values by the '=' character (ASCII character 61), even if
     *       the value is empty. Pairs of parameter and values are separated
     *       by the '&' character (ASCII code 38).
     *
     */
    private static String calculateStringToSignV2(
            Map<String, String> parameters, String serviceUrl, String resourcePath)
            throws SignatureException, URISyntaxException {
        // Sort the parameters alphabetically by storing
        // in TreeMap structure
        Map<String, String> sorted = new TreeMap<String, String>();
        sorted.putAll(parameters);

        // Set endpoint value
        URI endpoint = new URI(serviceUrl.toLowerCase());

        // Create flattened (String) representation
        StringBuilder data = new StringBuilder();
        data.append("GET\n");
        data.append(endpoint.getHost());
        data.append("\n" + resourcePath);
        data.append("\n");

        Iterator<Entry<String, String>> pairs =
                sorted.entrySet().iterator();
        while (pairs.hasNext()) {
            Map.Entry<String, String> pair = pairs.next();
            if (pair.getValue() != null) {
                data.append( pair.getKey() + "=" + pair.getValue());
            }
            else {
                data.append( pair.getKey() + "=");
            }

            // Delimit parameters with ampersand (&)
            if (pairs.hasNext()) {
                data.append( "&");
            }
        }
        return data.toString();
    }

    /*
     * Sign the text with the given secret key and convert to base64
     */
    private static String sign(String data, String secretKey)
            throws NoSuchAlgorithmException, InvalidKeyException,
            IllegalStateException, UnsupportedEncodingException {
        Mac mac = Mac.getInstance(ALGORITHM);
        mac.init(new SecretKeySpec(secretKey.getBytes(CHARACTER_ENCODING),
                ALGORITHM));
        byte[] signature = mac.doFinal(data.getBytes(CHARACTER_ENCODING));
        String signatureBase64 = new String(Base64.encodeBase64(signature),
                CHARACTER_ENCODING);
        return new String(signatureBase64);
    }

    private static String urlEncode(String rawValue) {
        String value = (rawValue == null) ? "" : rawValue;
        String encoded = null;

        try {
            encoded = URLEncoder.encode(value, CHARACTER_ENCODING)
                    .replace("+", "%20")
                    .replace("*", "%2A")
                    .replace("%7E","~");
        } catch (UnsupportedEncodingException e) {
            System.err.println("Unknown encoding: " + CHARACTER_ENCODING);
            e.printStackTrace();
        }
        return encoded;
    }

    public static void printCURL(String domain, String resourcePath, HashMap<String, String> parameters, String signature){

        // Add signature to the parameters
        parameters.put("Signature", urlEncode(signature));

        //Add public key to the parameters
        String publicKey = "-----BEGIN PUBLIC KEY-----\n" +

                "-----END PUBLIC KEY-----";
        parameters.put("PublicKey", urlEncode(publicKey));

        System.out.println("\n--- You Request should look like this ---");

        StringBuilder sampleRequest = new StringBuilder();
        sampleRequest.append(domain);
        sampleRequest.append(resourcePath + "?");
        Iterator<Entry<String, String>> pairsCurlParams = parameters.entrySet().iterator();
        while (pairsCurlParams.hasNext()) {
            Map.Entry<String, String> pair = pairsCurlParams.next();
            if (pair.getValue() != null) {
                if(pair.getKey().equalsIgnoreCase("SellerId")){
                    sampleRequest.append( "MerchantId=" + pair.getValue());
                }
                else {
                    sampleRequest.append( pair.getKey() + "=" + pair.getValue());
                }
            }
            else {
                if(pair.getKey().equalsIgnoreCase("SellerId")){
                    sampleRequest.append( "MerchantId=");
                }
                else {
                    sampleRequest.append(pair.getKey() + "=");
                }
            }

            // Delimit parameters with ampersand (&)
            if (pairsCurlParams.hasNext()) {
                sampleRequest.append( "&");
            }
        }
        System.out.println(sampleRequest.toString());
    }
}