All Products
Search
Document Center

Compute Nest:Compute Nest digital signature verification instructions

Last Updated:May 09, 2025

This topic describes how to verify digital signature information.

Background information

When you initiate API calls within Compute Nest ECS (such as calling CheckoutLicense, PushMeteringData, etc.), Compute Nest returns digital signature information (the Token field). Service providers can use the methods described below to calculate the value of the digital signature information and compare the calculated digital signature with the one returned by Compute Nest to determine whether the data has been tampered with.

Messaging process

This example shows how to calculate the digital signature value using the return value of CheckoutLicense.

  1. Call CheckoutLicense to obtain the return value.

    curl -H "Content-Type: application/json" -XPOST https://cn-wulanchabu.axt.aliyun.com/computeNest/license/check_out_license -d '{}'

    Return value

    {
        "code":200,
        "requestId":"4ea52d12-8e28-440b-b454-938d0518****",
        "instanceId":"i-0jl1ej1czubkimg6****",
        "result":{
            "RequestId":"CF54B4C9-E54C-1405-9A37-A0FE3D60****",
            "ServiceInstanceId":"si-85a343279cf341c2****",
            "LicenseMetadata":"{\"TemplateName\":\"Custom_Image_Ecs\",\"SpecificationName\":\"dataDiskSize\",\"CustomData\":\"30T\"}",
            "Token":"21292abff855ab5c2a03809e0e4fb048",
            "ExpireTime":"2022-11-10T08:03:16Z"
        }
    }
  2. Extract the parameter fields, remove the digital signature (Token) information, sort the parameters alphabetically, and concatenate them with the & symbol.

    The concatenated string is as follows:

    ExpireTime=2022-11-02T02:39:43Z&LicenseMetadata={"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}&RequestId=CF54B4C9-E54C-1405-9A37-A0FE3D60xxxx&ServiceInstanceId=si-85a343279cf341c2xxxx
    Note

    For services with LicenseMetadata, note the following to avoid signature inconsistencies during the signing process:

    • Parameters in the template should not contain Chinese characters (such as package names or template names).

    • When using, convert the returned JSON to {"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}.

  3. Add the service key to the end of the sorted string.

    The service key string format is: Key={ServiceProviderKey}. You can obtain the service key from the service details page.获取服务密钥

    The string with the service key added is as follows:

    ExpireTime=2022-11-02T02:39:43Z&LicenseMetadata={"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}&RequestId=CF54B4C9-E54C-1405-9A37-A0FE3D60xxxx&ServiceInstanceId=si-85a343279cf341c2xxxx&Key=37131c4a485141xxxxxx
  4. Use the MD5 encryption algorithm to encrypt the processed string, resulting in a 32-character lowercase value.

    After encryption, the value is: 21292abff855ab5c2a03809e0e4fb048.

    Service providers can compare the Token value calculated using the above method with the Token value returned by Compute Nest. If the two values are identical, it indicates that the data has not been tampered with.

Sample code

Note

The sample responses in the code can be obtained through Verify service instance validity, PushMeteringData - Push metering data.

Python

import json
import hashlib

def format_value(value):
    """Format parameter values, handling different parameter types"""
    if isinstance(value, bool):
        return "true" if value else "false"
    elif isinstance(value, dict):
        # Handle dictionary type, generate key=value format, key-value pairs separated by comma and space
        items = []
        for k, v in value.items():
            # Convert boolean values to lowercase strings, other values to strings
            v_str = str(v).lower() if isinstance(v, bool) else str(v)
            items.append(f"{k}={v_str}")
        return "{" + ", ".join(items) + "}"
    elif isinstance(value, list):
        # Handle list type, convert directly to JSON format
        return json.dumps(value, separators=(',', ':'))
    elif isinstance(value, str):
        try:
            # Try to parse string as JSON object (such as Components value)
            parsed = json.loads(value)
            if isinstance(parsed, dict):
                # If it's a dictionary, convert to standard JSON format
                return json.dumps(parsed, separators=(',', ':'))
            elif isinstance(parsed, list):
                return json.dumps(parsed, separators=(',', ':'))
            else:
                return value
        except json.JSONDecodeError:
            return value
    else:
        return str(value)

def calculate_token(response_json_str, service_key):
    response = json.loads(response_json_str)
    result = response.get("result", {})
    
    # Extract all non-Token parameters and format them
    params = {}
    for key, value in result.items():
        if key.lower() != "token":
            formatted_value = format_value(value)
            params[key] = formatted_value
    
    # Sort parameters alphabetically
    sorted_params = sorted(params.items(), key=lambda x: x[0].lower())
    
    # Generate concatenated string
    query_string = "&".join(f"{k}={v}" for k, v in sorted_params)
    print(f"Generated concatenated string: {query_string}")
    
    # Add service key
    final_str = f"{query_string}&Key={service_key}"
    print(f"String after adding service key: {final_str}")
    
    # Calculate MD5
    md5_hash = hashlib.md5(final_str.encode()).hexdigest().lower()
    
    return md5_hash
    
# Example usage
if __name__ == "__main__":
    
    # Sample response (replace with actual response)
    response_json = r'''{
        "code": 200,
        "requestId": "4ea52d12-8e28-440b-b454-938d0518****",
        "instanceId": "i-0jl1ej1czubkimg6****",
        "result": {
            "RequestId": "CF54B4C9-E54C-1405-9A37-A0FE3D60****",
            "ServiceInstanceId": "si-85a343279cf341c2****",
            "LicenseMetadata": "{\"TemplateName\":\"Custom_Image_Ecs\",\"SpecificationName\":\"dataDiskSize\",\"CustomData\":\"30T\"}",
            "Token": "21292abff855ab5c2a03809e0e4fb048",
            "ExpireTime": "2022-11-10T08:03:16Z"
        }
    }'''
    
    service_key = "37131c4a485141xxxxxx"  # Replace with actual service key
    
    calculated_token = calculate_token(response_json, service_key)
    print(f"Calculated Token: {calculated_token}")
    print(f"Returned Token: {json.loads(response_json)['result']['Token']}")
    
    # Verify consistency
    print(f"Verification result: {calculated_token == json.loads(response_json)['result']['Token']}")

Java

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.MessageDigest;
import java.util.*;

public class TokenVerifier {

    private static final String REQUEST_ID = "RequestId";
    private static final String TOKEN_KEY = "Token";

    // --- Test case ---
    public static void main(String[] args) {
        String responseJson = "{\n" +
                "    \"code\": 200,\n" +
                "    \"result\": {\n" +
                "        \"ExpireTime\": \"2022-11-02T02:39:43Z\",\n" +
                "        \"LicenseMetadata\": \"{\\\"TemplateName\\\":\\\"Custom_Image_Ecs\\\",\\\"SpecificationName\\\":\\\"dataDiskSize\\\",\\\"CustomData\\\":\\\"30T\\\"}\",\n" +
                "        \"RequestId\": \"CF54B4C9-E54C-1405-9A37-A0FE3D60****\",\n" +
                "        \"ServiceInstanceId\": \"si-85a343279cf341c2****\",\n" +
                "        \"Token\": \"21292abff855ab5c2a03809e0e4fb048\"\n" +
                "    }\n" +
                "}";

        String serviceProviderKey = "37131c4a485141xxxxxx";
        boolean isValid = verifyToken(responseJson, serviceProviderKey);
        System.out.println("Token verification result: " + isValid);
    }

    /**
     * Calculate and verify Token based on JSON response
     */
    public static boolean verifyToken(String responseJsonStr, String serviceProviderKey) {
        try {
            // 1. Parse JSON response
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode responseNode = objectMapper.readTree(responseJsonStr);
            JsonNode resultNode = responseNode.get("result");

            // 2. Extract parameters and format them
            Map<String, String> params = new HashMap<>();
            Iterator<String> fieldNames = resultNode.fieldNames();
            while (fieldNames.hasNext()) {
                String key = fieldNames.next();
                if (!key.equalsIgnoreCase(TOKEN_KEY)) {
                    Object value = resultNode.get(key);
                    String formattedValue = formatValue(value);
                    params.put(key, formattedValue);
                }
            }

            // 3. Sort parameters
            List<Map.Entry<String, String>> sortedParams = new ArrayList<>(params.entrySet());
            sortedParams.sort(Comparator.comparing(entry -> entry.getKey().toLowerCase()));

            // 4. Generate concatenated string
            String urlParams = buildOrderUrlParams(sortedParams);
            String finalStr = urlParams + "&Key=" + serviceProviderKey;
            System.out.println("Concatenated string: " + finalStr);

            // 5. Calculate MD5
            String calculatedToken = generateMD5(finalStr);
            
            // 6. Get returned Token
            String returnedToken = resultNode.get(TOKEN_KEY).asText();
            System.out.println("Calculated Token: " + calculatedToken);
            System.out.println("Returned Token: " + returnedToken);

            // 7. Verify consistency
            return calculatedToken.equals(returnedToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private static String formatValue(Object value) {
        if (value instanceof Boolean) {
            return (Boolean) value ? "true" : "false";
        } else if (value instanceof JsonNode) {
            JsonNode node = (JsonNode) value;
            if (node.isObject()) {
                // Handle object type, generate key=value format
                List<String> items = new ArrayList<>();
                Iterator<String> fieldNames = node.fieldNames();
                while (fieldNames.hasNext()) {
                    String key = fieldNames.next();
                    Object childValue = node.get(key);
                    String formattedChildValue = formatValue(childValue);
                    items.add(key + "=" + formattedChildValue);
                }
                return "{" + String.join(", ", items) + "}";
            } else if (node.isArray()) {
                // Handle array type (as needed)
                return node.toString().replace(" ", "");
            } else {
                return node.asText();
            }
        } else {
            return value.toString();
        }
    }

    private static String buildOrderUrlParams(List<Map.Entry<String, String>> entries) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : entries) {
            sb.append(entry.getKey())
                    .append("=")
                    .append(entry.getValue())
                    .append("&");
        }
        if (sb.length() > 0) {
            sb.setLength(sb.length() - 1); // Remove the last &
        }
        return sb.toString();
    }

    private static String generateMD5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hash = md.digest(input.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = String.format("%02x", b);
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (Exception e) {
            throw new RuntimeException("MD5 calculation failed", e);
        }
    }
}
Note

Ensure that your project includes the com.fasterxml.jackson.core library (such as Maven dependency):

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version> <!-- Recommended to use the latest stable version -->
</dependency>