Sign a request and validate the signature

Before calling an API, you must sign all HTTPS API requests to ensure security, and then validate the response signature accordingly.

Sign a request

The following graphic shows an overview of the signature creation process

image

Figure 1. Signature creation process 

Procedure

  1. Obtain your private key (privateKey) to sign a request.
  2. Construct the content to be signed: Content_To_Be_Signed.
  3. Calculate and generate the signature.
  4. Add the generated signature to the request header.

For details of each step, see the following examples.

Example

Step 1: Obtain your private key to sign a request

Get your private key ready, which is used to generate the signature later. You can create asymmetric keys in your own way or by using the tool provided in Alipay Developer Center. You can set up and then exchange keys for the sandbox and production environments respectively with the provided tool. The following figure shows where you can obtain the key generation tool and configure the key:

image

Besides using the tool to generate RSA2 key pair, you can also generate the RSA2 key pairs manually, for example:

copy
# 1. Generating the private key
openssl genrsa -out client_private_key_php_dotnet.pem

# 2. If you are a Java developer, convert the private key to PKCS8 format
openssl pkcs8 -topk8 -inform PEM -in client_private_key_php_dotnet.pem -outform PEM -nocrypt -out client_private_key_pkcs8.pem 

# 3. Generate the public key
openssl rsa -in client_private_key_php_dotnet.pem -pubout -out client_public_key_php_dotnet.pem 

# 4. Generate the private key that can be used in Java
cat client_private_key_pkcs8.pem | grep -v "^\-" | tr -d "\n" | sed 's/%$//' > client_private_key_java.pem

# 5. Generate the public key that can be used in Java
cat client_public_key_php_dotnet.pem | grep -v "^\-" | tr -d "\n" | sed 's/%$//' > client_public_key_java.pem

Step 2: Construct the content to be signed

Create the string to sign. The string to be signed is:

copy
<HTTP Method> <HTTP-URI-with-query-string>
<Client-Id>.<Request-Time|Response-Time>.<HTTP body>
  • HTTP Method: POST
  • HTTP-URI-with-query-string: For example, if the HTTP URL is https://xxx/openapi/transfer/transfer, this field is /openapi/transfer/transfer.
  • Client-Id: is used to identify a client, and is associated with the keys that are used for signature.  You can get this field from the request header.
  • Request-Time: Specifies the time when a request is sent, as defined by RFC3339. This field must be accurate to milliseconds. For example, 2019-05-28T12:12:12+08:00. You can get this field from the request header.
  • HTTP body: is the data body of the request.

Step 3: Calculate and generate the signature

Generate the signature. Use the algorithm and private key obtained in step 1 to generate the signature. The following example assumes that RSA256 algorithm is used to generate the signature, the string-to-sign var is obtained from step 2:

copy
signature=base64UrlEncode(sha256withrsa(<unsignedContent>), <privateKey>))
  • <unsignedContent>: is the content to be signed.
  • algorithm: RSA 256
  • <privateKey>:is the private key value.

Demo code:

copy
/**
     * 
     * @param requestURI // domain part excluded, sample: /ams/api/v1/payments/pay
     * @param clientId
     * @param requestTime
     * @param privateKey
     * @param requestBody
     * @return
     */
    public static String sign(String requestURI, String clientId, String requestTime,
                              String privateKey, String requestBody) {

        String content = String.format("POST %s\n%s.%s.%s", requestURI, clientId, requestTime,
            requestBody);

        try {
            java.security.Signature signature = java.security.Signature
                .getInstance("SHA256withRSA");

            PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(
                new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey.getBytes("UTF-8"))));

            signature.initSign(priKey);
            signature.update(content.getBytes("UTF-8"));

            byte[] signed = signature.sign();

            return URLEncoder.encode(new String(Base64.encodeBase64(signed), "UTF-8"), "UTF-8");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

Step 4: Add the generated signature to the request header

Add the signature to header. Assemble the signature algorithm, the key version used for the signature, and the signature into Signature header.

copy
key: Signature ;
value:algorithm=<algorithm>,keyVersion=<key-version>,signature=<signature>
  • algorithm , keyVersion : see the header of the Message structure chapter.
  • signature : the signature that is generated in step 3.

Validate a signature 

When handling the response, it is necessary to validate the signature. The following graphic shows an overview of the signature validation process: 

image

Figure 2. Signature validation process 

Procedure

  1. Obtain the platform public key.
  2. Construct the content to be validated: Content_To_Be_Validated.
  3. Get the signature from the response header.
  4. Validate the signature.

Example

Step 1: Obtain the platform public key

Obtain the public key, see Obtain your private key to sign a request for details. Obtain Client-Id and algorithm from the header.

Step 2: Construct the content to be validated

Create the string to be validated. The string to be signed is:

copy
<HTTP Method> <HTTP-URI-with-query-string>
<Client-Id>.<Request-Time|Response-Time>.<HTTP body>
  • HTTP Method: POST
  • HTTP-URI-with-query-string: For example, if the HTTP URL is https://xxx/openapi/transfer/transfer, this field is /openapi/transfer/transfer.
  • Client-Id: is used to identify a client, and is associated with the keys that are used for signature.  You can get this field from the request header.
  • Response-Time: Specifies the time when a request is sent, as defined by RFC3339. This field must be accurate to milliseconds. For example, 2019-05-28T12:12:14+08:00. You can get this field from the response header.
  • HTTP body: the data body of the response.

Step 3: Get the signature from the response header

The signature string ( signature ) can be extracted from the Signature  header of the response. For details about the response header, see the Message structure chapter.

Step 4: Validate the signature

Use the algorithm obtained in step 1 to calculate a digest of the string you created in step 2. Then, decrypt the signature by using the public key to get a digest. If the two digests match, the signature is verified. For example, if RSA256 algorithm is used, base64url decodes the signature content to obtain the original signature and validates the signature by using the sender's public key and sha256withrsa algorithm. 

copy
sha256withrsa_verify(base64UrlDecode(<signature>), <content_to_be_verified>, <serverPublicKey>)
  • sha256withrsa_verify: is the method to verify the signature.
  • base64UrlDecode: is the method to decode the signature.
  • signature: is signature string from the response.
  • content_to_be_verified: is the content to be validated that is created from step 2.
  • serverPublicKey: is the platform public key obtained from step 1.

Demo code:

copy
/**
     * 
     * @param requestURI // domain part excluded, sample: /ams/api/v1/payments/pay
     * @param clientId
     * @param reponseTime
     * @param alipayPublicKey
     * @param responseBody
     * @param signatureToBeVerified
     * @return
     */
    public static boolean verify(String requestURI, String clientId, String reponseTime,
                                 String alipayPublicKey, String responseBody,
                                 String signatureToBeVerified) {

        //signatureToBeVerified would not be present in the response when AMS returns a SIGNATURE_INVALID
        if (StringUtil.isBlank(signatureToBeVerified)) {
            return false;
        }

        String content = String.format("POST %s\n%s.%s.%s", requestURI, clientId, reponseTime,
            responseBody);

        try {
            java.security.Signature signature = java.security.Signature
                .getInstance("SHA256withRSA");

            PublicKey pubKey = KeyFactory.getInstance("RSA").generatePublic(
                new X509EncodedKeySpec(Base64.decodeBase64(alipayPublicKey.getBytes("UTF-8"))));

            signature.initVerify(pubKey);
            signature.update(content.getBytes("UTF-8"));

            return signature.verify(Base64.decodeBase64(URLDecoder.decode(signatureToBeVerified,
                "UTF-8").getBytes("UTF-8")));

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }