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.
Call
CheckoutLicenseto 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" } }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-85a343279cf341c2xxxxNoteFor 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"}.
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=37131c4a485141xxxxxxUse 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
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);
}
}
}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>