All Products
Search
Document Center

Alibaba Cloud SDK:Request body and signature mechanism for V3

Last Updated:Sep 25, 2025

If you prefer not to use an SDK to call Alibaba Cloud OpenAPI, or if your runtime environment does not support SDKs, you can call the API by self-signing your requests. This topic describes the V3 signature mechanism to help you make direct HTTP requests to Alibaba Cloud OpenAPI.

Instructions

  • If you use V2 signatures for API calls, you can directly replace them with V3 signatures.

  • Alibaba Cloud provides SDKs for products on the OpenAPI Portal. The APIs for these products support V3 signatures. Note that some cloud products use self-managed gateways with different authentication mechanisms. When you send HTTP requests to these products, refer to their respective signature documentation.

HTTP request structure

A complete Alibaba Cloud OpenAPI request consists of the following components.

Name

Required

Description

Example

Protocol

Yes

You can configure the protocol by referring to the API reference of the specific cloud product. Requests can be sent over HTTP or HTTPS. For better security, we recommend that you use HTTPS. Valid values are https:// and http://.

https://

Endpoint

Yes

The endpoint of the service. You can find the endpoints for different services and regions in the endpoint documentation of each cloud product.

ecs.cn-shanghai.aliyuncs.com

resource_URI_parameters

Yes

The API URL, which includes the API path and request parameters in the path and query.

ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai

RequestHeader

Yes

Common request header information. It usually includes the API version, Host, and Authorization. This is described in detail later.

Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

x-acs-action: RunInstances

host: ecs.cn-shanghai.aliyuncs.com

x-acs-date: 2023-10-26T09:01:01Z

x-acs-version: 2014-05-26

x-acs-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

x-acs-signature-nonce: d410180a5abf7fe235dd9b74aca91fc0

RequestBody

Yes

The business request parameters defined in the body. You can obtain the parameter details from the OpenAPI metadata.

HTTPMethod

Yes

You can obtain the request method from the OpenAPI metadata.

POST

RequestHeader

When you call an Alibaba Cloud OpenAPI, the request headers must include the following common information.

Name

Type

Required

Description

Example

host

String

Yes

The endpoint. For more information, see HTTP request structure.

ecs.cn-shanghai.aliyuncs.com

x-acs-action

String

Yes

The operation that you want to perform. You can visit the Alibaba Cloud OpenAPI Developer Portal and search for the OpenAPI that you want to call.

RunInstances

x-acs-content-sha256

String

Yes

The result of hashing the RequestBody and then Base16 encoding it. This is consistent with HashedRequestPayload.

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

x-acs-date

String

Yes

The UTC time in ISO 8601 format: `yyyy-MM-ddTHH:mm:ssZ`. For example, `2018-01-01T12:00:00Z`. The value must be within 15 minutes of when the request is sent.

2023-10-26T10:22:32Z

x-acs-signature-nonce

String

Yes

A unique random number for the signature. This random number prevents replay attacks. Use a different random number for each request. This mechanism applies only to the HTTP protocol.

3156853299f313e23d1673dc12e1703d

x-acs-version

String

Yes

The API version. For more information about how to obtain the version, see How do I obtain the API version (x-acs-version)?.

2014-05-26

Authorization

String

Required for non-anonymous requests

The authentication information used to verify the request. The format is `Authorization: SignatureAlgorithm Credential=AccessKeyId,SignedHeaders=SignedHeaders,Signature=Signature`.

SignatureAlgorithm is the signature encryption method, which is `ACS3-HMAC-SHA256`.

Credential is the user's AccessKey ID. You can view your AccessKey ID in the RAM console. To create an AccessKey pair, see Create an AccessKey pair.

SignedHeaders are the keys of the header fields included in the signature. [Note]: For enhanced security, we recommend that you sign all common request headers except for Authorization.

Signature is the request signature. For more information about its value, see Signature mechanism.

ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

x-acs-security-token

String

Required for STS authentication

The value of SecurityToken returned from the AssumeRole API call.

Signature mechanism

Signatures are created and authenticated using an AccessKey pair. For each HTTP or HTTPS request, the Alibaba Cloud API gateway recalculates the signature based on the request parameters. The gateway then compares the recalculated signature with the one provided in the request to verify the requester's identity. This process ensures data integrity and security.

Important

Requests and responses are encoded in the UTF-8 character set.

Step 1: Create a canonical request

The following pseudocode shows how to create a canonical request (CanonicalRequest):

CanonicalRequest = HTTPRequestMethod + '\n' + // The uppercase HTTP request method CanonicalURI + '\n' + // The canonical URI CanonicalQueryString + '\n' + // The canonical query string CanonicalHeaders + '\n' + // The canonical headers SignedHeaders + '\n' + // The signed headers HashedRequestPayload	// The hashed request body

HTTPRequestMethod (request method)

The uppercase HTTP method, such as GET or POST.

CanonicalURI (canonical URI)

This is the URI-encoded path of the URL. The path is the part of the URL between the host and the query string. It includes the forward slash (/) after the host but does not include the question mark (?) before the query string. The URI in your request must be a canonical URI. Use the UTF-8 character set to encode each part of the URI (the strings separated by /) according to the rules in RFC3986:

  • Characters A-Z, a-z, 0-9, and the characters -, _, ., and ~ are not encoded.

    Other characters are encoded as % followed by the hexadecimal value of their ASCII code. For example, a double quotation mark (") is encoded as %22. Note that some special characters require special handling, as shown in the following table:

    Before encoding

    After encoding

    Space ( )

    %20

    Asterisk (*)

    %2A

    %7E

    Tilde (~)

If you use java.net.URLEncoder from the Java standard library, you can first use the encode method for encoding. Then, replace plus signs (+) with %20, asterisks (*) with %2A, and %7E with tildes (~) in the encoded string. This provides the encoded string described by the preceding rules.

Important

For RPC-style APIs, use a forward slash (/) as the CanonicalURI.

For ROA-style APIs, this parameter is the value of path in the API metadata. For example, /api/v1/clusters.

CanonicalQueryString (canonical query string)

In the OpenAPI metadata, if the request parameter information for an API contains "in":"query", you must concatenate these request parameters as follows:

  1. Sort the request parameters in ascending alphabetical order by parameter name.

  2. Use the UTF-8 character set to URI-encode each parameter name and value according to the rules in RFC3986. The encoding rules are the same as those for CanonicalURI in the previous section.

  3. Connect the encoded parameter name and value with an equal sign (=). If a parameter has no value, use an empty string as its value.

  4. Connect multiple request parameters with ampersands (&).

Important

Example:

ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai

HashedRequestPayload

The HashedRequestPayload is obtained by hashing the RequestBody and then Base16-encoding the result. Update the value of x-acs-content-sha256 in the request headers to the value of HashedRequestPayload. The following pseudocode shows how to calculate the HashedRequestPayload:

HashedRequestPayload = HexEncode(Hash(RequestBody))
  • In the OpenAPI metadata, if the request parameter information for an API includes "in": "body" or "in": "formData", you must pass the parameters in the RequestBody:

    Note

    If no request parameters are passed in the RequestBody, the RequestBody is an empty string.

    • If the request parameter information includes "in": "formData", the parameters must be concatenated into a string in the format key1=value1&key2=value2&key3=value3. You must also add content-type=application/x-www-form-urlencoded to the request headers. Note that if a request parameter is an array or object, you must convert its value into indexed key-value pairs.

    • If the request parameter information includes "in": "body", you must add the content-type to the request headers. The value of content-type depends on the content type of the request. For example:

      • If the request content is JSON data, set content-type to application/json.

      • If the request content is a binary file stream, set content-type to application/octet-stream.

  • Hash is a message digest function. Currently, only the SHA256 algorithm is supported.

  • HexEncode is an encoding function that returns the digest in lowercase hexadecimal format (Base16 encoding).

Example value of HashedRequestPayload when RequestBody is empty:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

CanonicalHeaders (canonical request headers)

Construct the canonical headers by processing the parameters in the request headers as follows:

  1. Filter the parameters in the request headers that have the x-acs- prefix, or are host or content-type.

  2. Convert the parameter names to lowercase and sort them in ascending alphabetical order.

  3. Trim any leading or trailing whitespace from the parameter values.

  4. Connect the parameter name and value with a colon (:) and add a line feed (\n) at the end to form a canonical header entry (CanonicalHeaderEntry).

  5. Concatenate multiple canonical header entries into a single string.

Note

All request header parameters that meet the requirements, except for Authorization, must be included in the signature.

The pseudocode is as follows:

CanonicalHeaderEntry = Lowercase(HeaderName) + ':' + Trim(HeaderValue) + '\n' CanonicalHeaders = CanonicalHeaderEntry0 + CanonicalHeaderEntry1 + ... + CanonicalHeaderEntryN

Example:

host:ecs.cn-shanghai.aliyuncs.com x-acs-action:RunInstances x-acs-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 x-acs-date:2023-10-26T10:22:32Z x-acs-signature-nonce:3156853299f313e23d1673dc12e1703d x-acs-version:2014-05-26

SignedHeaders (list of signed headers)

This specifies the common request headers that are included in the signature for this request. The header names correspond one-to-one with those in CanonicalHeaders. Construct the list as follows:

  • Convert the names of the headers included in CanonicalHeaders to lowercase.

  • Sort the names in ascending alphabetical order and separate them with semicolons (;).

The pseudocode is as follows:

SignedHeaders = Lowercase(HeaderName0) + ';' + Lowercase(HeaderName1) + ... + Lowercase(HeaderNameN) 

Example:

host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version

Step 2: Create the string to sign

Create the string to sign (stringToSign) according to the following pseudocode:

StringToSign = SignatureAlgorithm + '\n' + HashedCanonicalRequest
  • SignatureAlgorithm

    SignatureAlgorithm: The signature protocol. Currently, only the ACS3-HMAC-SHA256 algorithm is supported.

  • HashedCanonicalRequest

    HashedCanonicalRequest: The hashed canonical request string. The following pseudocode shows how to calculate it:

    HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
    • Hash is a message digest function. Currently, only the SHA256 algorithm is supported.

    • HexEncode is an encoding function that returns the digest in lowercase hexadecimal format (Base16 encoding).

Example:

ACS3-HMAC-SHA256 7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259

Step 3: Calculate the signature

Calculate the signature value (Signature) according to the following pseudocode.

Signature = HexEncode(SignatureMethod(Secret, StringToSign))
  • StringToSign: The string to sign created in Step 2, encoded in UTF-8.

  • SignatureMethod: Use HMAC-SHA256 as the signature algorithm.

  • Secret: Your AccessKey secret.

  • HexEncode: An encoding function that returns the digest in lowercase hexadecimal format (Base16 encoding).

Example:

06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

Step 4: Add the signature to the request

After you calculate the signature, add the signature to the request by constructing the Authorization header in the following format: Authorization:<SignatureAlgorithm> Credential=<AccessKeyId>,SignedHeaders=<SignedHeaders>,Signature=<Signature>.

Example:

ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0

Signature example code

To help you better understand the signature mechanism, this section provides complete implementation examples in popular programming languages. The example code is for demonstration purposes and may not be universally applicable. Alibaba Cloud OpenAPI provides SDKs for various programming languages and development frameworks. Using an SDK eliminates the need for manual signature calculation and helps you quickly build applications that interact with Alibaba Cloud. We recommend that you use the provided SDKs.

Important

Before you create the signature, you must check the API metadata to obtain information such as the request method, request parameter names, parameter types, and how parameters are passed. Otherwise, the signature is very likely to fail.

Fixed parameter example

This example uses hypothetical parameter values to show the correct output at each step of the signature process. You can use these hypothetical parameter values in your code to verify that your signature calculation is correct by comparing your output with the output in this example.

Parameter name

Hypothetical parameter value

AccessKeyID

YourAccessKeyId

AccessKeySecret

YourAccessKeySecret

x-acs-signature-nonce

3156853299f313e23d1673dc12e1703d

x-acs-date

2023-10-26T10:22:32Z

x-acs-action

RunInstances

x-acs-version

2014-05-26

host

ecs.cn-shanghai.aliyuncs.com

API request parameters:

ImageId

win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd

RegionId

cn-shanghai

The signature process is as follows:

  1. Create a canonical request.

POST / ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai host:ecs.cn-shanghai.aliyuncs.com x-acs-action:RunInstances x-acs-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 x-acs-date:2023-10-26T10:22:32Z x-acs-signature-nonce:3156853299f313e23d1673dc12e1703d x-acs-version:2014-05-26 host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
  1. Create the string to sign.

ACS3-HMAC-SHA256 7ea06492da5221eba5297e897ce16e55f964061054b7695beedaac1145b1e259
  1. Calculate the signature.

06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0
  1. Add the signature to the request.

POST /?ImageId=win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd&RegionId=cn-shanghai HTTP/1.1 Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81484da74bceab24a1d853912eee15083a6f0f3283c0 x-acs-action: RunInstances host: ecs.cn-shanghai.aliyuncs.com x-acs-date: 2023-10-26T09:01:01Z x-acs-version: 2014-05-26 x-acs-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 x-acs-signature-nonce: d410180a5abf7fe235dd9b74aca91fc0 user-agent: AlibabaCloud (Mac OS X; x86_64) Java/1.8.0_352-b08 tea-util/0.2.6 TeaDSL/1 accept: application/json

Java example

Note

The example code runs in a JDK 1.8 environment. You may need to adjust the code based on your specific setup.

To run the Java example, add the following Maven dependencies to your pom.xml file.

<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.9.0</version> </dependency>
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.http.client.methods.*; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; public class SignatureDemo { public static class SignatureRequest { // HTTP Method private final String httpMethod; // Request path private final String canonicalUri; // Endpoint private final String host; // API name private final String xAcsAction; // API version private final String xAcsVersion; // Headers private final Map<String, String> headers = new TreeMap<>(); // Body parameters private byte[] body; // Query parameters private final Map<String, Object> queryParam = new TreeMap<>(); public SignatureRequest(String httpMethod, String canonicalUri, String host, String xAcsAction, String xAcsVersion) { this.httpMethod = httpMethod; this.canonicalUri = canonicalUri; this.host = host; this.xAcsAction = xAcsAction; this.xAcsVersion = xAcsVersion; initHeader(); } private void initHeader() { headers.put("host", host); headers.put("x-acs-action", xAcsAction); headers.put("x-acs-version", xAcsVersion); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); headers.put("x-acs-date", sdf.format(new Date())); headers.put("x-acs-signature-nonce", UUID.randomUUID().toString()); } public String getHttpMethod() { return httpMethod; } public String getCanonicalUri() { return canonicalUri; } public String getHost() { return host; } public Map<String, String> getHeaders() { return headers; } public byte[] getBody() { return body; } public Map<String, Object> getQueryParam() { return queryParam; } public void setBody(byte[] body) { this.body = body; } public void setQueryParam(String key, Object value) { this.queryParam.put(key, value); } public void setHeaders(String key, String value) { this.headers.put(key, value); } } public static class SignatureService { private static final String ALGORITHM = "ACS3-HMAC-SHA256"; /** * Calculate the signature */ public static void getAuthorization(SignatureRequest signatureRequest, String accessKeyId, String accessKeySecret, String securityToken) { try { // Process complex query parameters Map<String, Object> processedQueryParams = new TreeMap<>(); processObject(processedQueryParams, "", signatureRequest.getQueryParam()); signatureRequest.getQueryParam().clear(); signatureRequest.getQueryParam().putAll(processedQueryParams); // Step 1: Create a canonical request string String canonicalQueryString = buildCanonicalQueryString(signatureRequest.getQueryParam()); // Calculate the request body hash String hashedRequestPayload = calculatePayloadHash(signatureRequest.getBody()); signatureRequest.setHeaders("x-acs-content-sha256", hashedRequestPayload); // Add the security token if it exists if (securityToken != null && !securityToken.isEmpty()) { signatureRequest.setHeaders("x-acs-security-token", securityToken); } // Build the canonical headers and signed headers CanonicalHeadersResult canonicalHeadersResult = buildCanonicalHeaders(signatureRequest.getHeaders()); // Build the canonical request String canonicalRequest = String.join("\n", signatureRequest.getHttpMethod(), signatureRequest.getCanonicalUri(), canonicalQueryString, canonicalHeadersResult.canonicalHeaders, canonicalHeadersResult.signedHeaders, hashedRequestPayload); System.out.println("canonicalRequest=========>\n" + canonicalRequest); // Step 2: Create the string to sign String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8)); String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest; System.out.println("stringToSign=========>\n" + stringToSign); // Step 3: Calculate the signature String signature = DatatypeConverter.printHexBinary( hmac256(accessKeySecret.getBytes(StandardCharsets.UTF_8), stringToSign)) .toLowerCase(); System.out.println("signature=========>" + signature); // Step 4: Build the Authorization header String authorization = String.format("%s Credential=%s,SignedHeaders=%s,Signature=%s", ALGORITHM, accessKeyId, canonicalHeadersResult.signedHeaders, signature); System.out.println("authorization=========>" + authorization); signatureRequest.getHeaders().put("Authorization", authorization); } catch (Exception e) { throw new RuntimeException("Failed to generate authorization", e); } } /** * Process parameters of the formData request parameter type. */ private static String formDataToString(Map<String, Object> formData) { Map<String, Object> tileMap = new HashMap<>(); processObject(tileMap, "", formData); StringBuilder result = new StringBuilder(); boolean first = true; String symbol = "&"; for (Map.Entry<String, Object> entry : tileMap.entrySet()) { String value = String.valueOf(entry.getValue()); if (value != null && !value.isEmpty()) { if (first) { first = false; } else { result.append(symbol); } result.append(percentCode(entry.getKey())); result.append("="); result.append(percentCode(value)); } } return result.toString(); } /** * Build the canonical query string */ private static String buildCanonicalQueryString(Map<String, Object> queryParams) { return queryParams.entrySet().stream() .map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))) .collect(Collectors.joining("&")); } /** * Calculate the request body hash */ private static String calculatePayloadHash(byte[] body) throws Exception { if (body != null) { return sha256Hex(body); } else { return sha256Hex("".getBytes(StandardCharsets.UTF_8)); } } /** * Build the canonical headers */ private static CanonicalHeadersResult buildCanonicalHeaders(Map<String, String> headers) { List<Map.Entry<String, String>> signedHeaders = headers.entrySet().stream() .filter(entry -> { String key = entry.getKey().toLowerCase(); return key.startsWith("x-acs-") || "host".equals(key) || "content-type".equals(key); }) .sorted(Map.Entry.comparingByKey()) .collect(Collectors.toList()); StringBuilder canonicalHeaders = new StringBuilder(); StringBuilder signedHeadersString = new StringBuilder(); for (Map.Entry<String, String> entry : signedHeaders) { String lowerKey = entry.getKey().toLowerCase(); String value = entry.getValue().trim(); canonicalHeaders.append(lowerKey).append(":").append(value).append("\n"); signedHeadersString.append(lowerKey).append(";"); } if (signedHeadersString.length() > 0) { signedHeadersString.setLength(signedHeadersString.length() - 1); // Remove the last semicolon } return new CanonicalHeadersResult(canonicalHeaders.toString(), signedHeadersString.toString()); } private static class CanonicalHeadersResult { final String canonicalHeaders; final String signedHeaders; CanonicalHeadersResult(String canonicalHeaders, String signedHeaders) { this.canonicalHeaders = canonicalHeaders; this.signedHeaders = signedHeaders; } } /** * Process complex object parameters */ private static void processObject(Map<String, Object> map, String key, Object value) { if (value == null) { return; } if (key == null) { key = ""; } if (value instanceof List<?>) { List<?> list = (List<?>) value; for (int i = 0; i < list.size(); ++i) { processObject(map, key + "." + (i + 1), list.get(i)); } } else if (value instanceof Map<?, ?>) { Map<?, ?> subMap = (Map<?, ?>) value; for (Map.Entry<?, ?> entry : subMap.entrySet()) { processObject(map, key + "." + entry.getKey().toString(), entry.getValue()); } } else { if (key.startsWith(".")) { key = key.substring(1); } if (value instanceof byte[]) { map.put(key, new String((byte[]) value, StandardCharsets.UTF_8)); } else { map.put(key, String.valueOf(value)); } } } /** * HMAC-SHA256 calculation */ private static byte[] hmac256(byte[] secretKey, String str) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm()); mac.init(secretKeySpec); return mac.doFinal(str.getBytes(StandardCharsets.UTF_8)); } /** * SHA-256 hash calculation */ private static String sha256Hex(byte[] input) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] digest = md.digest(input); return DatatypeConverter.printHexBinary(digest).toLowerCase(); } /** * URL encoding */ public static String percentCode(String str) { if (str == null) { return ""; } try { return URLEncoder.encode(str, "UTF-8") .replace("+", "%20") .replace("*", "%2A") .replace("%7E", "~"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 encoding not supported", e); } } } /** * Signature example. You need to replace the example parameters in the main method based on your actual requirements. * The logic for obtaining the value of canonicalUri is the only difference between ROA and RPC APIs. Other parts are similar. * <p> * Obtain the request method (methods), request parameter name (name), parameter type (type), and parameter location (in) from the API metadata, and encapsulate the parameters into SignatureRequest. * 1. If a request parameter is specified as "in":"query" in the metadata, pass it as a queryParam. Note: For RPC APIs, this type of parameter can also be passed in the body with content-type set to application/x-www-form-urlencoded. See Example 3. * 2. If a request parameter is specified as "in": "body" in the metadata, pass it in the body. The MIME type can be application/octet-stream or application/json. Note: For RPC APIs, we do not recommend using application/json. You can use the method in Example 3 instead. * 3. If a request parameter is specified as "in": "formData" in the metadata, pass it in the body. The MIME type is application/x-www-form-urlencoded. */ public static void main(String[] args) throws IOException { // Obtain the AccessKey pair from environment variables String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"); String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); String securityToken = System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN"); if (accessKeyId == null || accessKeySecret == null) { System.err.println("Set the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables."); return; } // RPC API example 1: Request parameter is "in":"query". This example uses DescribeInstanceStatus of ECS. SignatureRequest signatureRequest = new SignatureRequest( "POST", "/", "ecs.cn-hangzhou.aliyuncs.com", "DescribeInstanceStatus", "2014-05-26" ); signatureRequest.setQueryParam("RegionId", "cn-hangzhou"); signatureRequest.setQueryParam("InstanceId", Arrays.asList("i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX")); /*// RPC API example 2: Request parameter is "in":"body" (file upload scenario). This example uses RecognizeGeneral of OCR. SignatureRequest signatureRequest = new SignatureRequest( "POST", "/", "ocr-api.cn-hangzhou.aliyuncs.com", "RecognizeGeneral", "2021-07-07"); signatureRequest.setBody(Files.readAllBytes(Paths.get("D:\\test.jpeg"))); signatureRequest.setHeaders("content-type", "application/octet-stream");*/ /*// RPC API example 3: Request parameter is "in": "formData" or "in":"body" (non-file upload scenario). This example uses TranslateGeneral of Machine Translation. String httpMethod = "POST"; String canonicalUri = "/"; String host = "mt.aliyuncs.com"; String xAcsAction = "TranslateGeneral"; String xAcsVersion = "2018-10-12"; SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); Map<String, Object> body = new HashMap<>(); body.put("FormatType", "text"); body.put("SourceLanguage", "zh"); body.put("TargetLanguage", "en"); body.put("SourceText", "Hello"); body.put("Scene", "general"); String formDataToString = SignatureService.formDataToString(body); signatureRequest.setBody(formDataToString.getBytes(StandardCharsets.UTF_8)); signatureRequest.setHeaders("content-type", "application/x-www-form-urlencoded");*/ /*// ROA API POST request example. This example uses creating a cluster in Container Service for Kubernetes (ACK). SignatureRequest signatureRequest = new SignatureRequest( "POST", "/clusters", "cs.cn-chengdu.aliyuncs.com", "CreateCluster", "2015-12-15"); TreeMap<String, Object> body = new TreeMap<>(); body.put("name", "test"); body.put("cluster_type", "ManagedKubernetes"); body.put("kubernetes_version", "1.34.1-aliyun.1"); body.put("region_id", "cn-chengdu"); body.put("snat_entry", true); body.put("deletion_protection", true); body.put("proxy_mode", "ipvs"); body.put("profile", "Default"); body.put("timezone", "Asia/Shanghai"); body.put("cluster_spec", "ack.pro.small"); body.put("enable_rrsa", false); body.put("service_cidr", "192.168.0.0/16"); body.put("zone_ids", Arrays.asList("cn-chengdu-b","cn-chengdu-b")); Gson gson = (new GsonBuilder()).disableHtmlEscaping().create(); signatureRequest.setBody(gson.toJson(body).getBytes(StandardCharsets.UTF_8)); signatureRequest.setHeaders("content-type", "application/json");*/ /*// ROA API GET request example. This example uses querying cluster information in ACK. SignatureRequest signatureRequest = new SignatureRequest( "GET", "/clusters/" + SignatureService.percentCode("c299f90b63b************") + "/resources", "cs.cn-chengdu.aliyuncs.com", "DescribeClusterResources", "2015-12-15"); signatureRequest.setQueryParam("with_addon_resources", true);*/ /*// ROA API DELETE request example. This example uses deleting a cluster. SignatureRequest signatureRequest = new SignatureRequest( "DELETE", "/clusters/" + SignatureService.percentCode("c299f90b63b************"), "cs.cn-chengdu.aliyuncs.com", "DeleteCluster", "2015-12-15");*/ // Generate the signature SignatureService.getAuthorization(signatureRequest, accessKeyId, accessKeySecret, securityToken); // Test whether the API can be called successfully callApi(signatureRequest); } /** * For testing only */ private static void callApi(SignatureRequest signatureRequest) { try { String url = "https://" + signatureRequest.getHost() + signatureRequest.getCanonicalUri(); URIBuilder uriBuilder = new URIBuilder(url); // Add query parameters for (Map.Entry<String, Object> entry : signatureRequest.getQueryParam().entrySet()) { uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue())); } HttpUriRequest httpRequest; switch (signatureRequest.getHttpMethod()) { case "GET": httpRequest = new HttpGet(uriBuilder.build()); break; case "POST": HttpPost httpPost = new HttpPost(uriBuilder.build()); if (signatureRequest.getBody() != null) { httpPost.setEntity(new ByteArrayEntity(signatureRequest.getBody(), ContentType.create(signatureRequest.getHeaders().get("content-type")))); } httpRequest = httpPost; break; case "DELETE": httpRequest = new HttpDelete(uriBuilder.build()); break; default: System.out.println("Unsupported HTTP method: " + signatureRequest.getHttpMethod()); throw new IllegalArgumentException("Unsupported HTTP method"); } // Add request headers for (Map.Entry<String, String> entry : signatureRequest.getHeaders().entrySet()) { httpRequest.addHeader(entry.getKey(), entry.getValue()); } // Send the request try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) { String result = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println("API Response: " + result); } } catch (IOException | URISyntaxException e) { throw new RuntimeException("Failed to call API", e); } } } 

Python example

Note

The example code runs in a Python 3.12.3 environment. You may need to adjust the code based on your specific setup.

You must manually install pytz and requests. Run the following commands in the terminal based on your Python version.

Python 3

pip3 install pytz pip3 install requests
import hashlib import hmac import json import os import uuid from collections import OrderedDict from datetime import datetime from typing import Any, Dict, List, Optional, Union from urllib.parse import quote_plus, urlencode import pytz import requests class SignatureRequest: def __init__( self, http_method: str, canonical_uri: str, host: str, x_acs_action: str, x_acs_version: str ): self.http_method = http_method self.canonical_uri = canonical_uri self.host = host self.x_acs_action = x_acs_action self.x_acs_version = x_acs_version self.headers = self._init_headers() self.query_param = OrderedDict() # type: Dict[str, Any] self.body = None # type: Optional[bytes] def _init_headers(self) -> Dict[str, str]: current_time = datetime.now(pytz.timezone('Etc/GMT')) headers = OrderedDict([ ('host', self.host), ('x-acs-action', self.x_acs_action), ('x-acs-version', self.x_acs_version), ('x-acs-date', current_time.strftime('%Y-%m-%dT%H:%M:%SZ')), ('x-acs-signature-nonce', str(uuid.uuid4())), ]) return headers def sorted_query_params(self) -> None: """Sort query parameters by name and return the encoded string.""" self.query_param = dict(sorted(self.query_param.items())) def sorted_headers(self) -> None: """Sort request headers by name and return the encoded string.""" self.headers = dict(sorted(self.headers.items())) def get_authorization(request: SignatureRequest) -> None: try: new_query_param = OrderedDict() process_object(new_query_param, '', request.query_param) request.query_param.clear() request.query_param.update(new_query_param) request.sorted_query_params() # Step 1: Create a canonical request string canonical_query_string = "&".join( f"{percent_code(quote_plus(k))}={percent_code(quote_plus(str(v)))}" for k, v in request.query_param.items() ) hashed_request_payload = sha256_hex(request.body or b'') request.headers['x-acs-content-sha256'] = hashed_request_payload if SECURITY_TOKEN: signature_request.headers["x-acs-security-token"] = SECURITY_TOKEN request.sorted_headers() filtered_headers = OrderedDict() for k, v in request.headers.items(): if k.lower().startswith("x-acs-") or k.lower() in ["host", "content-type"]: filtered_headers[k.lower()] = v canonical_headers = "\n".join(f"{k}:{v}" for k, v in filtered_headers.items()) + "\n" signed_headers = ";".join(filtered_headers.keys()) canonical_request = ( f"{request.http_method}\n{request.canonical_uri}\n{canonical_query_string}\n" f"{canonical_headers}\n{signed_headers}\n{hashed_request_payload}" ) print(canonical_request) # Step 2: Create the string to sign hashed_canonical_request = sha256_hex(canonical_request.encode("utf-8")) string_to_sign = f"{ALGORITHM}\n{hashed_canonical_request}" print(string_to_sign) # Step 3: Calculate the signature signature = hmac256(ACCESS_KEY_SECRET.encode("utf-8"), string_to_sign).hex().lower() # Step 4: Construct the Authorization header authorization = f'{ALGORITHM} Credential={ACCESS_KEY_ID},SignedHeaders={signed_headers},Signature={signature}' request.headers["Authorization"] = authorization except Exception as e: print("Failed to get authorization") print(e) def form_data_to_string(form_data: Dict[str, Any]) -> str: tile_map = OrderedDict() process_object(tile_map, "", form_data) return urlencode(tile_map) def process_object(result_map: Dict[str, str], key: str, value: Any) -> None: if value is None: return if isinstance(value, (list, tuple)): for i, item in enumerate(value): process_object(result_map, f"{key}.{i + 1}", item) elif isinstance(value, dict): for sub_key, sub_value in value.items(): process_object(result_map, f"{key}.{sub_key}", sub_value) else: key = key.lstrip(".") result_map[key] = value.decode("utf-8") if isinstance(value, bytes) else str(value) def hmac256(key: bytes, msg: str) -> bytes: return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() def sha256_hex(s: bytes) -> str: return hashlib.sha256(s).hexdigest() def call_api(request: SignatureRequest) -> None: url = f"https://{request.host}{request.canonical_uri}" if request.query_param: url += "?" + urlencode(request.query_param, doseq=True, safe="*") headers = dict(request.headers) data = request.body try: response = requests.request( method=request.http_method, url=url, headers=headers, data=data ) response.raise_for_status() print(response.text) except requests.RequestException as e: print("Failed to send request") print(e) def percent_code(encoded_str: str) -> str: return encoded_str.replace("+", "%20").replace("*", "%2A").replace("%7E", "~") # Obtain the AccessKey ID and AccessKey secret from environment variables ACCESS_KEY_ID = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID") ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET") SECURITY_TOKEN = os.environ.get("ALIBABA_CLOUD_SECURITY_TOKEN") ALGORITHM = "ACS3-HMAC-SHA256" """ Signature example. When testing, you can select an example in the main function and modify the example values based on your actual requirements. For example, to call SendSms, select Example 1 and modify http_method, host, x_acs_action, x_acs_version, and query_param. The logic for obtaining the value of canonicalUri is the only difference between ROA and RPC APIs. Obtain the request method (methods), request parameter name (name), parameter type (type), and parameter location (in) from the OpenAPI metadata, and encapsulate the parameters into SignatureRequest. 1. If a request parameter is specified as "in":"query" in the metadata, pass it as a queryParam without setting content-type. Note: For RPC APIs, this type of parameter can also be passed in the body with content-type set to application/x-www-form-urlencoded. See Example 3. 2. If a request parameter is specified as "in": "body" in the metadata, pass it in the body and set content-type based on your actual requirements. Note: For RPC APIs, we do not recommend using application/json. You can use the method in Example 3 instead. 3. If a request parameter is specified as "in": "formData" in the metadata, pass it in the body with content-type set to application/x-www-form-urlencoded. """ if __name__ == "__main__": # RPC API request example 1: Request parameter is "in":"query" http_method = "POST" # The request method, which can be obtained from the metadata. We recommend that you use POST. canonical_uri = "/" # RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. host = "ecs.cn-hangzhou.aliyuncs.com" # The endpoint of the cloud product. x_acs_action = "DescribeInstanceStatus" # The API name. x_acs_version = "2014-05-26" # The API version number. signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version) # The request parameters for DescribeInstanceStatus are as follows: # RegionId is of the String type, specified as "in":"query" in the metadata, and is required. signature_request.query_param['RegionId'] = 'cn-hangzhou' # InstanceId is of the array type, specified as "in":"query" in the metadata, and is optional. signature_request.query_param['InstanceId'] = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"] # # RPC API request example 2: Request parameter is "in":"body" (file upload scenario) # http_method = "POST" # canonical_uri = "/" # host = "ocr-api.cn-hangzhou.aliyuncs.com" # x_acs_action = "RecognizeGeneral" # x_acs_version = "2021-07-07" # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version) # # The request parameter is specified as "in": "body" in the metadata. Pass it in the body. # file_path = "D:\\test.png" # with open(file_path, 'rb') as file: # # Read the image content as a byte array # signature_request.body = file.read() # signature_request.headers["content-type"] = "application/octet-stream" # # RPC API request example 3: Request parameter is "in": "formData" or "in":"body" (non-file upload scenario) # http_method = "POST" # canonical_uri = "/" # host = "mt.aliyuncs.com" # x_acs_action = "TranslateGeneral" # x_acs_version = "2018-10-12" # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version) # # The request parameters for TranslateGeneral are as follows: # # Context is of the String type, specified as "in":"query" in the metadata, and is optional. # signature_request.query_param['Context'] = 'Morning' # # Parameters such as FormatType, SourceLanguage, and TargetLanguage are specified as "in":"formData" in the metadata. # form_data = OrderedDict() # form_data["FormatType"] = "text" # form_data["SourceLanguage"] = "zh" # form_data["TargetLanguage"] = "en" # form_data["SourceText"] = "Hello" # form_data["Scene"] = "general" # signature_request.body = bytes(form_data_to_string(form_data), 'utf-8') # signature_request.headers["content-type"] = "application/x-www-form-urlencoded" # # Example 4: ROA API POST request # http_method = "POST" # canonical_uri = "/clusters" # host = "cs.cn-beijing.aliyuncs.com" # x_acs_action = "CreateCluster" # x_acs_version = "2015-12-15" # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version) # The request parameter is specified as "in":"body" in the metadata. Pass it in the body. # body = OrderedDict() # body["name"] = "testDemo" # body["region_id"] = "cn-beijing" # body["cluster_type"] = "ExternalKubernetes" # body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX" # body["container_cidr"] = "172.16.1.0/20" # body["service_cidr"] = "10.2.0.0/24" # body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX" # body["vswitch_ids"] = ["vsw-2zei30dhfldu8XXXXXXXX"] # signature_request.body = bytes(json.dumps(body, separators=(',', ':')), 'utf-8') # signature_request.headers["content-type"] = "application/json; charset=utf-8" # # Example 5: ROA API GET request # http_method = "GET" # # If canonicalUri contains a path parameter, you need to encode it: percent_code({path_parameter}) # cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX") # canonical_uri = f"/clusters/{cluster_id_encode}/resources" # host = "cs.cn-beijing.aliyuncs.com" # x_acs_action = "DescribeClusterResources" # x_acs_version = "2015-12-15" # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version) # signature_request.query_param['with_addon_resources'] = True # # Example 6: ROA API DELETE request # http_method = "DELETE" # # If canonicalUri contains a path parameter, you need to encode it: percent_code({path_parameter}) # cluster_id_encode = percent_code("ca72cfced86db497cab79aa28XXXXXXXX") # canonical_uri = f"/clusters/{cluster_id_encode}" # host = "cs.cn-beijing.aliyuncs.com" # x_acs_action = "DeleteCluster" # x_acs_version = "2015-12-15" # signature_request = SignatureRequest(http_method, canonical_uri, host, x_acs_action, x_acs_version) get_authorization(signature_request) call_api(signature_request) 

Go example

Note

The example code runs in a go1.22.2 environment. You may need to adjust the code based on your specific setup.

Run the following commands in the terminal:

go get github.com/google/uuid go get golang.org/x/exp/maps
package main import (	"bytes"	"crypto/hmac"	"crypto/sha256"	"encoding/hex"	"io"	"os"	"sort"	"golang.org/x/exp/maps"	"fmt"	"net/http"	"net/url"	"strings"	"time"	"github.com/google/uuid" ) type Request struct {	httpMethod string	canonicalUri string	host string	xAcsAction string	xAcsVersion string	headers map[string]string	body []byte	queryParam map[string]interface{} } func NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion string) *Request {	req := &Request{	httpMethod: httpMethod,	canonicalUri: canonicalUri,	host: host,	xAcsAction: xAcsAction,	xAcsVersion: xAcsVersion,	headers: make(map[string]string),	queryParam: make(map[string]interface{}),	}	req.headers["host"] = host	req.headers["x-acs-action"] = xAcsAction	req.headers["x-acs-version"] = xAcsVersion	req.headers["x-acs-date"] = time.Now().UTC().Format(time.RFC3339)	req.headers["x-acs-signature-nonce"] = uuid.New().String()	return req } // os.Getenv() is used to obtain the AccessKey ID and AccessKey secret from environment variables. var (	AccessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")	AccessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")	SecurityToken = os.Getenv("ALIBABA_CLOUD_SECURITY_TOKEN")	ALGORITHM = "ACS3-HMAC-SHA256" ) // Signature example. You need to replace the example parameters in the main method based on your actual requirements. // The logic for obtaining the value of canonicalUri is the only difference between ROA and RPC APIs. Other parts are similar. // Obtain the request method (methods), request parameter name (name), parameter type (type), and parameter location (in) from the API metadata, and encapsulate the parameters into SignatureRequest. // 1. If a request parameter is specified as "in":"query" in the metadata, pass it as a queryParam. Note: For RPC APIs, this type of parameter can also be passed in the body with content-type set to application/x-www-form-urlencoded. See Example 3. // 2. If a request parameter is specified as "in": "body" in the metadata, pass it in the body. The MIME type can be application/octet-stream or application/json. For RPC APIs, we do not recommend using application/json. You can use the method in Example 3 instead. // 3. If a request parameter is specified as "in": "formData" in the metadata, pass it in the body. The MIME type is application/x-www-form-urlencoded. func main() {	// RPC API request example 1: Request parameter is "in":"query"	httpMethod := "POST" // The request method. Most RPC APIs support both POST and GET. This example uses POST.	canonicalUri := "/" // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI.	host := "ecs.cn-hangzhou.aliyuncs.com" // The endpoint of the cloud product.	xAcsAction := "DescribeInstanceStatus" // The API name.	xAcsVersion := "2014-05-26" // The API version number.	req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)	// The request parameters for DescribeInstanceStatus are as follows:	// RegionId is of the String type, specified as "in":"query" in the metadata, and is required.	req.queryParam["RegionId"] = "cn-hangzhou"	// InstanceId is of the array type, specified as "in":"query" in the metadata, and is optional.	instanceIds := []interface{}{"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"}	req.queryParam["InstanceId"] = instanceIds	// // RPC API request example 2: Request parameter is "in":"body" (file upload scenario)	// httpMethod := "POST"	// canonicalUri := "/"	// host := "ocr-api.cn-hangzhou.aliyuncs.com"	// xAcsAction := "RecognizeGeneral"	// xAcsVersion := "2021-07-07"	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)	// // Read the file content	// filePath := "D:\\test.png"	// bytes, err := os.ReadFile(filePath)	// if err != nil {	// fmt.Println("Error reading file:", err)	// return	// }	// req.body = bytes	// req.headers["content-type"] = "application/octet-stream"	// // RPC API request example 3: Request parameter is "in": "formData" or "in":"body" (non-file upload scenario)	// httpMethod := "POST"	// canonicalUri := "/"	// host := "mt.aliyuncs.com"	// xAcsAction := "TranslateGeneral"	// xAcsVersion := "2018-10-12"	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)	// // The request parameters for TranslateGeneral are as follows:	// // Context is of the String type, specified as "in":"query" in the metadata, and is optional.	// req.queryParam["Context"] = "Morning"	// // Parameters such as FormatType, SourceLanguage, and TargetLanguage are specified as "in":"formData" in the metadata.	// body := make(map[string]interface{})	// body["FormatType"] = "text"	// body["SourceLanguage"] = "zh"	// body["TargetLanguage"] = "en"	// body["SourceText"] = "Hello"	// body["Scene"] = "general"	// str := formDataToString(body)	// req.body = []byte(*str)	// req.headers["content-type"] = "application/x-www-form-urlencoded"	// // ROA API POST request	// httpMethod := "POST"	// canonicalUri := "/clusters"	// host := "cs.cn-beijing.aliyuncs.com"	// xAcsAction := "CreateCluster"	// xAcsVersion := "2015-12-15"	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)	// // Encapsulate request parameters. The request parameter is specified as "in": "body" in the metadata, which means the parameter is in the body.	// body := make(map[string]interface{})	// body["name"] = "testDemo"	// body["region_id"] = "cn-beijing"	// body["cluster_type"] = "ExternalKubernetes"	// body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"	// body["container_cidr"] = "10.0.0.0/8"	// body["service_cidr"] = "172.16.1.0/20"	// body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"	// vswitch_ids := []interface{}{"vsw-2zei30dhfldu8XXXXXXXX"}	// body["vswitch_ids"] = vswitch_ids	// jsonBytes, err := json.Marshal(body)	// if err != nil {	// fmt.Println("Error marshaling to JSON:", err)	// return	// }	// req.body = []byte(jsonBytes)	// req.headers["content-type"] = "application/json; charset=utf-8"	// // ROA API GET request	// httpMethod := "GET"	// // If canonicalUri contains a path parameter, you need to encode it: percentCode({path_parameter})	// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66") + "/resources"	// host := "cs.cn-beijing.aliyuncs.com"	// xAcsAction := "DescribeClusterResources"	// xAcsVersion := "2015-12-15"	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)	// req.queryParam["with_addon_resources"] = "true"	// // ROA API DELETE request	// httpMethod := "DELETE"	// // If canonicalUri contains a path parameter, you need to encode it: percentCode({path_parameter})	// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66")	// host := "cs.cn-beijing.aliyuncs.com"	// xAcsAction := "DeleteCluster"	// xAcsVersion := "2015-12-15"	// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)	// Signature process	getAuthorization(req)	// Call the API	error := callAPI(req)	if error != nil {	println(error.Error())	} } func callAPI(req *Request) error {	urlStr := "https://" + req.host + req.canonicalUri	q := url.Values{}	keys := maps.Keys(req.queryParam)	sort.Strings(keys)	for _, k := range keys {	v := req.queryParam[k]	q.Set(k, fmt.Sprintf("%v", v))	}	urlStr += "?" + q.Encode()	fmt.Println(urlStr)	httpReq, err := http.NewRequest(req.httpMethod, urlStr, strings.NewReader(string(req.body)))	if err != nil {	return err	}	for key, value := range req.headers {	httpReq.Header.Set(key, value)	}	client := &http.Client{}	resp, err := client.Do(httpReq)	if err != nil {	return err	}	defer func(Body io.ReadCloser) {	err := Body.Close()	if err != nil {	return	}	}(resp.Body)	var respBuffer bytes.Buffer	_, err = io.Copy(&respBuffer, resp.Body)	if err != nil {	return err	}	respBytes := respBuffer.Bytes()	fmt.Println(string(respBytes))	return nil } func getAuthorization(req *Request) {	// Flattens parameters in queryParam that have List or Map values.	newQueryParams := make(map[string]interface{})	processObject(newQueryParams, "", req.queryParam)	req.queryParam = newQueryParams	// Step 1: Create a canonical request string	canonicalQueryString := ""	keys := maps.Keys(req.queryParam)	sort.Strings(keys)	for _, k := range keys {	v := req.queryParam[k]	canonicalQueryString += percentCode(url.QueryEscape(k)) + "=" + percentCode(url.QueryEscape(fmt.Sprintf("%v", v))) + "&"	}	canonicalQueryString = strings.TrimSuffix(canonicalQueryString, "&")	fmt.Printf("canonicalQueryString========>%s\n", canonicalQueryString)	var bodyContent []byte	if req.body == nil {	bodyContent = []byte("")	} else {	bodyContent = req.body	}	hashedRequestPayload := sha256Hex(bodyContent)	req.headers["x-acs-content-sha256"] = hashedRequestPayload	if SecurityToken != "" {	req.headers["x-acs-security-token"] = SecurityToken	}	canonicalHeaders := ""	signedHeaders := ""	HeadersKeys := maps.Keys(req.headers)	sort.Strings(HeadersKeys)	for _, k := range HeadersKeys {	lowerKey := strings.ToLower(k)	if lowerKey == "host" || strings.HasPrefix(lowerKey, "x-acs-") || lowerKey == "content-type" {	canonicalHeaders += lowerKey + ":" + req.headers[k] + "\n"	signedHeaders += lowerKey + ";"	}	}	signedHeaders = strings.TrimSuffix(signedHeaders, ";")	canonicalRequest := req.httpMethod + "\n" + req.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload	fmt.Printf("canonicalRequest========>\n%s\n", canonicalRequest)	// Step 2: Create the string to sign	hashedCanonicalRequest := sha256Hex([]byte(canonicalRequest))	stringToSign := ALGORITHM + "\n" + hashedCanonicalRequest	fmt.Printf("stringToSign========>\n%s\n", stringToSign)	// Step 3: Calculate the signature	byteData, err := hmac256([]byte(AccessKeySecret), stringToSign)	if err != nil {	fmt.Println(err)	panic(err)	}	signature := strings.ToLower(hex.EncodeToString(byteData))	// Step 4: Construct the Authorization header	authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature	req.headers["Authorization"] = authorization } func hmac256(key []byte, toSignString string) ([]byte, error) {	// Instantiate HMAC-SHA256 hash	h := hmac.New(sha256.New, key)	// Write the string to sign	_, err := h.Write([]byte(toSignString))	if err != nil {	return nil, err	}	// Calculate and return the signature	return h.Sum(nil), nil } func sha256Hex(byteArray []byte) string {	// Instantiate SHA-256 hash function	hash := sha256.New()	// Write the string to the hash function	_, _ = hash.Write(byteArray)	// Calculate the SHA-256 hash and convert it to a lowercase hexadecimal string	hexString := hex.EncodeToString(hash.Sum(nil))	return hexString } func percentCode(str string) string {	// Replace specific encoded characters	str = strings.ReplaceAll(str, "+", "%20")	str = strings.ReplaceAll(str, "*", "%2A")	str = strings.ReplaceAll(str, "%7E", "~")	return str } func formDataToString(formData map[string]interface{}) *string {	tmp := make(map[string]interface{})	processObject(tmp, "", formData)	res := ""	urlEncoder := url.Values{}	for key, value := range tmp {	v := fmt.Sprintf("%v", value)	urlEncoder.Add(key, v)	}	res = urlEncoder.Encode()	return &res } // processObject recursively processes an object, expanding complex objects (like Maps and Lists) into flat key-value pairs. func processObject(mapResult map[string]interface{}, key string, value interface{}) {	if value == nil {	return	}	switch v := value.(type) {	case []interface{}:	for i, item := range v {	processObject(mapResult, fmt.Sprintf("%s.%d", key, i+1), item)	}	case map[string]interface{}:	for subKey, subValue := range v {	processObject(mapResult, fmt.Sprintf("%s.%s", key, subKey), subValue)	}	default:	if strings.HasPrefix(key, ".") {	key = key[1:]	}	if b, ok := v.([]byte); ok {	mapResult[key] = string(b)	} else {	mapResult[key] = fmt.Sprintf("%v", v)	}	} } 

Node.js example

Note

The example code runs in a Node.js v20.13.1 environment. You may need to adjust the code based on your specific setup.

This example uses the Node.js language.

const crypto = require('crypto'); const fs = require('fs'); class Request { constructor(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion) { this.httpMethod = httpMethod; this.canonicalUri = canonicalUri || '/'; this.host = host; this.xAcsAction = xAcsAction; this.xAcsVersion = xAcsVersion; this.headers = {}; this.body = null; this.queryParam = {}; this.initHeader(); } initHeader() { const date = new Date(); this.headers = { 'host': this.host, 'x-acs-action': this.xAcsAction, 'x-acs-version': this.xAcsVersion, 'x-acs-date': date.toISOString().replace(/\..+/, 'Z'), 'x-acs-signature-nonce': crypto.randomBytes(16).toString('hex') } } } const ALGORITHM = 'ACS3-HMAC-SHA256'; const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID; const accessKeySecret = process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET; const securityToken = process.env.ALIBABA_CLOUD_SECURITY_TOKEN; const encoder = new TextEncoder() if (!accessKeyId || !accessKeySecret) { console.error('ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables must be set.'); process.exit(1); } function getAuthorization(signRequest) { try { newQueryParam = {}; processObject(newQueryParam, "", signRequest.queryParam); signRequest.queryParam = newQueryParam; // Step 1: Create a canonical request string const canonicalQueryString = Object.entries(signRequest.queryParam) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${percentCode(key)}=${percentCode(value)}`) .join('&'); // The request body. When the request body is empty, such as in a GET request, RequestPayload is a fixed empty string. const requestPayload = signRequest.body || encoder.encode(''); const hashedRequestPayload = sha256Hex(requestPayload); signRequest.headers['x-acs-content-sha256'] = hashedRequestPayload; if (securityToken) { signRequest.headers['x-acs-security-token'] = securityToken; } // Convert all keys to lowercase signRequest.headers = Object.fromEntries( Object.entries(signRequest.headers).map(([key, value]) => [key.toLowerCase(), value]) ); const sortedKeys = Object.keys(signRequest.headers) .filter(key => key.startsWith('x-acs-') || key === 'host' || key === 'content-type') .sort(); // The list of signed headers. Multiple request header names (lowercase) are sorted alphabetically and separated by semicolons (;). const signedHeaders = sortedKeys.join(";") // Construct the request headers. Multiple canonical headers are concatenated after being sorted alphabetically by header name (lowercase). const canonicalHeaders = sortedKeys.map(key => `${key}:${signRequest.headers[key]}`).join('\n') + '\n'; const canonicalRequest = [ signRequest.httpMethod, signRequest.canonicalUri, canonicalQueryString, canonicalHeaders, signedHeaders, hashedRequestPayload ].join('\n'); console.log('canonicalRequest=========>\n', canonicalRequest); // Step 2: Create the string to sign const hashedCanonicalRequest = sha256Hex(encoder.encode(canonicalRequest)); const stringToSign = `${ALGORITHM}\n${hashedCanonicalRequest}`; console.log('stringToSign=========>', stringToSign); // Step 3: Calculate the signature const signature = hmac256(accessKeySecret, stringToSign); console.log('signature=========>', signature); // Step 4: Construct the Authorization header const authorization = `${ALGORITHM} Credential=${accessKeyId},SignedHeaders=${signedHeaders},Signature=${signature}`; console.log('authorization=========>', authorization); signRequest.headers['Authorization'] = authorization; } catch (error) { console.error('Failed to get authorization'); console.error(error); } } async function callApi(signRequest) { try { let url = `https://${signRequest.host}${signRequest.canonicalUri}`; // Add request parameters if (signRequest.queryParam) { const query = new URLSearchParams(signRequest.queryParam); url += '?' + query.toString(); } console.log('url=========>', url); // Configure request options let options = { method: signRequest.httpMethod.toUpperCase(), headers: signRequest.headers }; // Process the request body if (signRequest.body && ['POST', 'PUT'].includes(signRequest.httpMethod.toUpperCase())) { options.body = signRequest.body; } return (await fetch(url, options)).text(); } catch (error) { console.error('Failed to send request:', error); } } function percentCode(str) { return encodeURIComponent(str) .replace(/\+/g, '%20') .replace(/\*/g, '%2A') .replace(/~/g, '%7E'); } function hmac256(key, data) { const hmac = crypto.createHmac('sha256', key); hmac.update(data, 'utf8'); return hmac.digest('hex').toLowerCase(); } function sha256Hex(bytes) { const hash = crypto.createHash('sha256'); const digest = hash.update(bytes).digest('hex'); return digest.toLowerCase(); } function formDataToString(formData) { const tmp = {}; processObject(tmp, "", formData); let queryString = ''; for (let [key, value] of Object.entries(tmp)) { if (queryString !== '') { queryString += '&'; } queryString += encodeURIComponent(key) + '=' + encodeURIComponent(value); } return queryString; } function processObject(map, key, value) { // If the value is null, no further processing is needed. if (value === null) { return; } if (key === null) { key = ""; } // When the value is of type Array, iterate through each element in the Array and process it recursively. if (Array.isArray(value)) { value.forEach((item, index) => { processObject(map, `${key}.${index + 1}`, item); }); } else if (typeof value === 'object' && value !== null) { // When the value is of type Object, iterate through each key-value pair in the Object and process it recursively. Object.entries(value).forEach(([subKey, subValue]) => { processObject(map, `${key}.${subKey}`, subValue); }); } else { // For keys starting with ".", remove the leading "." to maintain key continuity. if (key.startsWith('.')) { key = key.slice(1); } map[key] = String(value); } } /** * Signature example. You need to replace the example parameters in the main method based on your actual requirements. * The logic for obtaining the value of canonicalUri is the only difference between ROA and RPC APIs. Other parts are similar. * * Obtain the request method (methods), request parameter name (name), parameter type (type), and parameter location (in) from the API metadata, and encapsulate the parameters into SignatureRequest. * 1. If a request parameter is specified as "in":"query" in the metadata, pass it as a queryParam. Note: For RPC APIs, this type of parameter can also be passed in the body with content-type set to application/x-www-form-urlencoded. See Example 3. * 2. If a request parameter is specified as "in": "body" in the metadata, pass it in the body. The MIME type can be application/octet-stream or application/json. For RPC APIs, we do not recommend using application/json. You can use the method in Example 3 instead. * 3. If a request parameter is specified as "in": "formData" in the metadata, pass it in the body. The MIME type is application/x-www-form-urlencoded. */ // RPC API request example 1: Request parameter is "in":"query" const httpMethod = 'POST'; // The request method. Most RPC APIs support both POST and GET. This example uses POST. const canonicalUri = '/'; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. const host = 'ecs.cn-hangzhou.aliyuncs.com'; // The endpoint. const xAcsAction = 'DescribeInstanceStatus'; // The API name. const xAcsVersion = '2014-05-26'; // The API version number. const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion); // The request parameters for DescribeInstanceStatus are as follows: signRequest.queryParam = { // RegionId is of the String type, specified as "in":"query" in the metadata, and is required. RegionId: 'cn-hangzhou', // InstanceId is of the array type, specified as "in":"query" in the metadata, and is optional. InstanceId: ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"], } // // RPC API request example 2: Request parameter is "in":"body" (file upload scenario) // const httpMethod = 'POST'; // const canonicalUri = '/'; // const host = 'ocr-api.cn-hangzhou.aliyuncs.com'; // const xAcsAction = 'RecognizeGeneral'; // const xAcsVersion = '2021-07-07'; // const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion); // const filePath = 'D:\\test.png'; // const bytes = fs.readFileSync(filePath); // // The request parameter is specified as "in": "body" in the metadata, which means the parameter is in the body. // signRequest.body = bytes; // signRequest.headers['content-type'] = 'application/octet-stream'; // // RPC API request example 3: Request parameter is "in": "formData" or "in":"body" (non-file upload scenario) // const httpMethod = 'POST'; // The request method. Most RPC APIs support both POST and GET. This example uses POST. // const canonicalUri = '/'; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. // const host = 'mt.aliyuncs.com'; // The endpoint. // const xAcsAction = 'TranslateGeneral'; // The API name. // const xAcsVersion = '2018-10-12'; // The API version number. // const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion); // // The request parameters for TranslateGeneral are as follows: // // Context is of the String type, specified as "in":"query" in the metadata, and is optional. // signRequest.queryParam["Context"] = "Morning"; // // Parameters such as FormatType, SourceLanguage, and TargetLanguage are specified as "in":"formData" in the metadata. // const formData = { // SourceLanguage: "zh", // TargetLanguage: "en", // FormatType: "text", // Scene: "general", // SourceText: 'Hello' // } // const str = formDataToString(formData) // signRequest.body = encoder.encode(str); // signRequest.headers['content-type'] = 'application/x-www-form-urlencoded'; // // ROA API POST request // const httpMethod = 'POST'; // const canonicalUri = '/clusters'; // const host = 'cs.cn-beijing.aliyuncs.com'; // const xAcsAction = 'CreateCluster'; // const xAcsVersion = '2015-12-15'; // const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion); // // The request parameter is specified as "in": "body" in the metadata, which means the parameter is in the body. // const body = { // name: 'testDemo', // region_id: 'cn-beijing', // cluster_type: 'ExternalKubernetes', // vpcid: 'vpc-2zeou1uod4ylaf35teei9', // container_cidr: '10.0.0.0/8', // service_cidr: '172.16.3.0/20', // security_group_id: 'sg-2ze1a0rlgeo7dj37dd1q', // vswitch_ids: [ // 'vsw-2zei30dhfldu8ytmtarro' // ], // } // signRequest.body = encoder.encode(JSON.stringify(body)); // signRequest.headers['content-type'] = 'application/json'; // // ROA API GET request // const httpMethod = 'GET'; // // If canonicalUri contains a path parameter, you need to encode it: percentCode({path_parameter}) // const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96") + '/resources'; // const host = 'cs.cn-beijing.aliyuncs.com'; // const xAcsAction = 'DescribeClusterResources'; // const xAcsVersion = '2015-12-15'; // const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion); // signRequest.queryParam = { // with_addon_resources: true, // } // // ROA API DELETE request // const httpMethod = 'DELETE'; // // If canonicalUri contains a path parameter, you need to encode it: percentCode({path_parameter}) // const canonicalUri = '/clusters/' + percentCode("c28c2615f8bfd466b9ef9a76c61706e96"); // const host = 'cs.cn-beijing.aliyuncs.com'; // const xAcsAction = 'DeleteCluster'; // const xAcsVersion = '2015-12-15'; // const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion, xAcsVersion); getAuthorization(signRequest); // Call the API callApi(signRequest).then(r => { console.log(r); }).catch(error => { console.error(error); }); 

PHP example

Note

The example code runs in a PHP 7.4.33 environment. You may need to adjust the code based on your specific setup.

<?php class SignatureDemo { // Encryption algorithm private $ALGORITHM; // Access Key ID private $AccessKeyId; // Access Key Secret private $AccessKeySecret; private $SecurityToken; public function __construct() { date_default_timezone_set('UTC'); // Set the time zone to GMT $this->AccessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); // getenv() is used to obtain the RAM user's AccessKey ID from an environment variable. $this->AccessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); // getenv() is used to obtain the RAM user's AccessKey secret from an environment variable. $this->SecurityToken = getenv('ALIBABA_CLOUD_SECURITY_TOKEN'); $this->ALGORITHM = 'ACS3-HMAC-SHA256'; // Set the encryption algorithm } /** * Signature example. You need to replace the example parameters in the main method based on your actual requirements. * The logic for obtaining the value of canonicalUri is the only difference between ROA and RPC APIs. Other parts are similar. * * Obtain the request method (methods), request parameter name (name), parameter type (type), and parameter location (in) from the API metadata, and encapsulate the parameters into SignatureRequest. * 1. If a request parameter is specified as "in":"query" in the metadata, pass it as a queryParam. Note: For RPC APIs, this type of parameter can also be passed in the body with content-type set to application/x-www-form-urlencoded. See Example 3. * 2. If a request parameter is specified as "in": "body" in the metadata, pass it in the body. The MIME type can be application/octet-stream or application/json. For RPC APIs, we do not recommend using application/json. You can use the method in Example 3 instead. * 3. If a request parameter is specified as "in": "formData" in the metadata, pass it in the body. The MIME type is application/x-www-form-urlencoded. */ public function main() { // RPC API request example 1: Request parameter is "in":"query" $request = $this->createRequest('POST', '/', 'ecs.cn-hangzhou.aliyuncs.com', 'DescribeInstanceStatus', '2014-05-26'); // The request parameters for DescribeInstanceStatus are as follows: $request['queryParam'] = [ // RegionId is of the String type, specified as "in":"query" in the metadata, and is required. 'RegionId' => 'cn-hangzhou', // InstanceId is of the array type, specified as "in":"query" in the metadata, and is optional. 'InstanceId' => ["i-bp11ht4h2kdXXXXXXXX", "i-bp16maz3h3xgXXXXXXXX", "i-bp10r67hmslXXXXXXXX"] ]; // // RPC API request example 2: Request parameter is "in":"body" (file upload scenario) // $request = $this->createRequest('POST', '/', 'ocr-api.cn-hangzhou.aliyuncs.com', 'RecognizeGeneral', '2021-07-07'); // // The request parameter is specified as "in": "body" in the metadata. Pass it in the body. // $filePath = 'D:\\test.png'; // // Use a file resource to pass the binary file // $fileResource = fopen($filePath, 'rb'); // $request['body'] = stream_get_contents($fileResource); // $request['headers']['content-type'] = 'application/octet-stream'; // Set Content-Type to application/octet-stream // // Close the file resource // fclose($fileResource); // // RPC API request example 3: Request parameter is "in": "formData" or "in":"body" (non-file upload scenario) // $request = $this->createRequest('POST', '/', 'mt.aliyuncs.com', 'TranslateGeneral', '2018-10-12'); // // The request parameters for TranslateGeneral are as follows: // $request['queryParam'] = [ // // Context is of the String type, specified as "in":"query" in the metadata, and is optional. // 'Context' => 'Morning', // ]; // $formData = [ // 'FormatType' => 'text', // 'SourceLanguage' => 'zh', // 'TargetLanguage' => 'en', // 'SourceText' => 'Hello', // 'Scene' => 'general', // ]; // $str = self::formDataToString($formData); // $request['body'] = $str; // $request['headers']['content-type'] = 'application/x-www-form-urlencoded'; // // ROA API POST request // $request = $this->createRequest('POST', '/clusters', 'cs.cn-beijing.aliyuncs.com', 'CreateCluster', '2015-12-15'); // $bodyData = [ // 'name' => 'Test Cluster', // 'region_id' => 'cn-beijing', // 'cluster_type' => 'ExternalKubernetes', // 'vpcid' => 'vpc-2zeou1uod4ylaXXXXXXXX', // 'service_cidr' => '10.2.0.0/24', // 'security_group_id' => 'sg-2ze1a0rlgeo7XXXXXXXX', // "vswitch_ids" => [ // "vsw-2zei30dhfldu8XXXXXXXX" // ] // ]; // $request['body'] = json_encode($bodyData, JSON_UNESCAPED_UNICODE); // $request['headers']['content-type'] = 'application/json; charset=utf-8'; // // ROA API GET request // // If canonicalUri contains a path parameter, you need to encode it: rawurlencode({path_parameter}) // $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX'; // $canonicalUri = sprintf("/clusters/%s/resources", rawurlencode($cluster_id)); // $request = $this->createRequest('GET', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DescribeClusterResources', '2015-12-15'); // $request['queryParam'] = [ // 'with_addon_resources' => true, // ]; // // ROA API DELETE request // $cluster_id = 'c930976b3b1fc4e02bc09831dXXXXXXXX'; // $canonicalUri = sprintf("/clusters/%s", rawurlencode($cluster_id)); // $request = $this->createRequest('DELETE', $canonicalUri, 'cs.cn-beijing.aliyuncs.com', 'DeleteCluster', '2015-12-15'); $this->getAuthorization($request); // Call the API $this->callApi($request); } private function createRequest($httpMethod, $canonicalUri, $host, $xAcsAction, $xAcsVersion) { $headers = [ 'host' => $host, 'x-acs-action' => $xAcsAction, 'x-acs-version' => $xAcsVersion, 'x-acs-date' => gmdate('Y-m-d\TH:i:s\Z'), 'x-acs-signature-nonce' => bin2hex(random_bytes(16)), ]; return [ 'httpMethod' => $httpMethod, 'canonicalUri' => $canonicalUri, 'host' => $host, 'headers' => $headers, 'queryParam' => [], 'body' => null, ]; } private function getAuthorization(&$request) { $request['queryParam'] = $this->processObject($request['queryParam']); $canonicalQueryString = $this->buildCanonicalQueryString($request['queryParam']); $hashedRequestPayload = hash('sha256', $request['body'] ?? ''); $request['headers']['x-acs-content-sha256'] = $hashedRequestPayload; if($this->SecurityToken){ $request['headers']['x-acs-security-token'] = $this->SecurityToken; } $canonicalHeaders = $this->buildCanonicalHeaders($request['headers']); $signedHeaders = $this->buildSignedHeaders($request['headers']); $canonicalRequest = implode("\n", [ $request['httpMethod'], $request['canonicalUri'], $canonicalQueryString, $canonicalHeaders, $signedHeaders, $hashedRequestPayload, ]); $hashedCanonicalRequest = hash('sha256', $canonicalRequest); $stringToSign = "{$this->ALGORITHM}\n$hashedCanonicalRequest"; $signature = strtolower(bin2hex(hash_hmac('sha256', $stringToSign, $this->AccessKeySecret, true))); $authorization = "{$this->ALGORITHM} Credential={$this->AccessKeyId},SignedHeaders=$signedHeaders,Signature=$signature"; $request['headers']['Authorization'] = $authorization; } private function callApi($request) { try { // Send the request using cURL $url = "https://" . $request['host'] . $request['canonicalUri']; // Add request parameters to the URL if (!empty($request['queryParam'])) { $url .= '?' . http_build_query($request['queryParam']); } echo $url; // Initialize a cURL session $ch = curl_init(); // Set cURL options curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL certificate verification. This reduces security and should not be used in a production environment. (Not recommended!) curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the content instead of outputting it curl_setopt($ch, CURLOPT_HTTPHEADER, $this->convertHeadersToArray($request['headers'])); // Add request headers // Set cURL options based on the request type switch ($request['httpMethod']) { case "GET": break; case "POST": curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $request['body']); break; case "DELETE": curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); break; default: echo "Unsupported HTTP method: " . $request['body']; throw new Exception("Unsupported HTTP method"); } // Send the request $result = curl_exec($ch); // Check for errors if (curl_errno($ch)) { echo "Failed to send request: " . curl_error($ch); } else { echo $result; } } catch (Exception $e) { echo "Error: " . $e->getMessage(); } finally { // Close the cURL session curl_close($ch); } } function formDataToString($formData) { $res = self::processObject($formData); return http_build_query($res); } function processObject($value) { // If the value is null, no further processing is needed. if ($value === null) { return; } $tmp = []; foreach ($value as $k => $v) { if (0 !== strpos($k, '_')) { $tmp[$k] = $v; } } return self::flatten($tmp); } private static function flatten($items = [], $delimiter = '.', $prepend = '') { $flatten = []; foreach ($items as $key => $value) { $pos = \is_int($key) ? $key + 1 : $key; if (\is_object($value)) { $value = get_object_vars($value); } if (\is_array($value) && !empty($value)) { $flatten = array_merge( $flatten, self::flatten($value, $delimiter, $prepend . $pos . $delimiter) ); } else { if (\is_bool($value)) { $value = true === $value ? 'true' : 'false'; } $flatten["$prepend$pos"] = $value; } } return $flatten; } private function convertHeadersToArray($headers) { $headerArray = []; foreach ($headers as $key => $value) { $headerArray[] = "$key: $value"; } return $headerArray; } private function buildCanonicalQueryString($queryParams) { ksort($queryParams); // Build and encode query parameters $params = []; foreach ($queryParams as $k => $v) { if (null === $v) { continue; } $str = rawurlencode($k); if ('' !== $v && null !== $v) { $str .= '=' . rawurlencode($v); } else { $str .= '='; } $params[] = $str; } return implode('&', $params); } private function buildCanonicalHeaders($headers) { // Sort headers by key and concatenate them uksort($headers, 'strcasecmp'); $canonicalHeaders = ''; foreach ($headers as $key => $value) { $canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n"; } return $canonicalHeaders; } private function buildSignedHeaders($headers) { // Build the signed headers string $signedHeaders = array_keys($headers); sort($signedHeaders, SORT_STRING | SORT_FLAG_CASE); return implode(';', array_map('strtolower', $signedHeaders)); } } $demo = new SignatureDemo(); $demo->main();

.NET example

Note

The example code runs in a .NET 8.0.302 environment. You may need to adjust the code based on your specific setup.

using System.Globalization; using System.Net; using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; using System.Web; using Newtonsoft.Json; namespace SignatureDemo { public class Request { public string HttpMethod { get; private set; } public string CanonicalUri { get; private set; } public string Host { get; private set; } public string XAcsAction { get; private set; } public string XAcsVersion { get; private set; } public SortedDictionary<string, object> Headers { get; private set; } public byte[]? Body { get; set; } public Dictionary<string, object> QueryParam { get; set; } public Request(string httpMethod, string canonicalUri, string host, string xAcsAction, string xAcsVersion) { HttpMethod = httpMethod; CanonicalUri = canonicalUri; Host = host; XAcsAction = xAcsAction; XAcsVersion = xAcsVersion; Headers = []; QueryParam = []; Body = null; InitHeader(); } private void InitHeader() { Headers["host"] = Host; Headers["x-acs-action"] = XAcsAction; Headers["x-acs-version"] = XAcsVersion; DateTime utcNow = DateTime.UtcNow; Headers["x-acs-date"] = utcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture); Headers["x-acs-signature-nonce"] = Guid.NewGuid().ToString(); } } public class Program { private static readonly string AccessKeyId = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_ID environment variable is not set."); private static readonly string AccessKeySecret = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET") ?? throw new InvalidOperationException("The ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable is not set."); private static readonly string? SecurityToken = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_SECURITY_TOKEN"); private const string Algorithm = "ACS3-HMAC-SHA256"; private const string ContentType = "content-type"; /** * Signature example. You need to replace the example parameters in the main method based on your actual requirements. * The logic for obtaining the value of canonicalUri is the only difference between ROA and RPC APIs. Other parts are similar. * * Obtain the request method (methods), request parameter name (name), parameter type (type), and parameter location (in) from the API metadata, and encapsulate the parameters into SignatureRequest. * 1. If a request parameter is specified as "in":"query" in the metadata, pass it as a queryParam. Note: For RPC APIs, this type of parameter can also be passed in the body with content-type set to application/x-www-form-urlencoded. See Example 3. * 2. If a request parameter is specified as "in": "body" in the metadata, pass it in the body. The MIME type can be application/octet-stream or application/json. For RPC APIs, we do not recommend using application/json. You can use the method in Example 3 instead. * 3. If a request parameter is specified as "in": "formData" in the metadata, pass it in the body. The MIME type is application/x-www-form-urlencoded. */ public static void Main(string[] args) { // RPC API request example 1: Request parameter is "in":"query" string httpMethod = "POST"; // The request method. Most RPC APIs support both POST and GET. This example uses POST. string canonicalUri = "/"; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. string host = "ecs.cn-hangzhou.aliyuncs.com"; // The endpoint of the cloud product. string xAcsAction = "DescribeInstanceStatus"; // The API name. string xAcsVersion = "2014-05-26"; // The API version number. var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); // The request parameters for DescribeInstanceStatus are as follows: // RegionId is of the String type, specified as "in":"query" in the metadata, and is required. request.QueryParam["RegionId"] = "cn-hangzhou"; // InstanceId is of the array type, specified as "in":"query" in the metadata, and is optional. List<string> instanceIds = ["i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"]; request.QueryParam["InstanceId"] = instanceIds; // // RPC API request example 2: Request parameter is "in":"body" (file upload scenario) // string httpMethod = "POST"; // string canonicalUri = "/"; // string host = "ocr-api.cn-hangzhou.aliyuncs.com"; // string xAcsAction = "RecognizeGeneral"; // string xAcsVersion = "2021-07-07"; // var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); // // The request parameter is specified as "in": "body" in the metadata. Pass it in the body. // request.Body = File.ReadAllBytes(@"D:\test.png"); // request.Headers["content-type"] = "application/octet-stream"; // // RPC API request example 3: Request parameter is "in": "formData" or "in":"body" (non-file upload scenario) // string httpMethod = "POST"; // string canonicalUri = "/"; // string host = "mt.aliyuncs.com"; // string xAcsAction = "TranslateGeneral"; // string xAcsVersion = "2018-10-12"; // var request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); // // The request parameters for TranslateGeneral are as follows: // // Context is of the String type, specified as "in":"query" in the metadata, and is optional. // request.QueryParam["Context"] = "Morning"; // // Parameters such as FormatType, SourceLanguage, and TargetLanguage are specified as "in":"formData" in the metadata. // var body = new Dictionary<string, object> // { // { "FormatType", "text" }, // { "SourceLanguage", "zh" }, // { "TargetLanguage", "en" }, // { "SourceText", "Hello" }, // { "Scene", "general" }, // }; // var str = FormDataToString(body); // request.Body = Encoding.UTF8.GetBytes(str); // request.Headers[ContentType] = "application/x-www-form-urlencoded"; // // ROA API POST request // String httpMethod = "POST"; // String canonicalUri = "/clusters"; // String host = "cs.cn-beijing.aliyuncs.com"; // String xAcsAction = "CreateCluster"; // String xAcsVersion = "2015-12-15"; // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); // // Request body. Convert the body to a JSON string using JsonConvert. // var body = new SortedDictionary<string, object> // { // { "name", "testDemo" }, // { "region_id", "cn-beijing" }, // { "cluster_type", "ExternalKubernetes" }, // { "vpcid", "vpc-2zeou1uod4ylaXXXXXXXX" }, // { "container_cidr", "10.0.0.0/8" }, // { "service_cidr", "172.16.1.0/20" }, // { "security_group_id", "sg-2ze1a0rlgeo7XXXXXXXX" }, // { "vswitch_ids", new List<string>{"vsw-2zei30dhfldu8XXXXXXXX"} }, // }; // string jsonBody = JsonConvert.SerializeObject(body, Formatting.None); // request.Body = Encoding.UTF8.GetBytes(jsonBody); // request.Headers[ContentType] = "application/json; charset=utf-8"; // // ROA API GET request // String httpMethod = "GET"; // // If canonicalUri contains a path parameter, you need to encode it: PercentCode({path_parameter}) // String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX") + "/resources"; // String host = "cs.cn-beijing.aliyuncs.com"; // String xAcsAction = "DescribeClusterResources"; // String xAcsVersion = "2015-12-15"; // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); // request.QueryParam["with_addon_resources"]=true; // // ROA API DELETE request // String httpMethod = "DELETE"; // // If canonicalUri contains a path parameter, you need to encode it: PercentCode({path_parameter}) // String canonicalUri = "/clusters/" + PercentCode("c81d501a467594eab873edbf2XXXXXXXX"); // String host = "cs.cn-beijing.aliyuncs.com"; // String xAcsAction = "DeleteCluster"; // String xAcsVersion = "2015-12-15"; // Request request = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion); GetAuthorization(request); // Call the API var result = CallApiAsync(request); Console.WriteLine($"result:{result.Result}"); } private static async Task<string?> CallApiAsync(Request request) { try { // Declare httpClient using var httpClient = new HttpClient(); // Build the URL string url = $"https://{request.Host}{request.CanonicalUri}"; var uriBuilder = new UriBuilder(url); var query = new List<string>(); // Add request parameters foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower())) { string value = entry.Value?.ToString() ?? ""; query.Add($"{entry.Key}={Uri.EscapeDataString(value)}"); } uriBuilder.Query = string.Join("&", query); Console.WriteLine(uriBuilder.Uri); var requestMessage = new HttpRequestMessage { Method = new HttpMethod(request.HttpMethod), RequestUri = uriBuilder.Uri, }; // Set request headers foreach (var entry in request.Headers) { if (entry.Key == "Authorization") { requestMessage.Headers.TryAddWithoutValidation("Authorization", entry.Value.ToString()); ; } else if (entry.Key == ContentType) // Must be consistent with the one defined in main { continue; } else { requestMessage.Headers.Add(entry.Key, entry.Value.ToString()); } } if (request.Body != null) { HttpContent content = new ByteArrayContent(request.Body); string contentType = request.Headers["content-type"].ToString(); content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); requestMessage.Content = content; } // Send the request HttpResponseMessage response = await httpClient.SendAsync(requestMessage); // Read the response content string result = await response.Content.ReadAsStringAsync(); return result; } catch (UriFormatException e) { Console.WriteLine("Invalid URI syntax"); Console.WriteLine(e.Message); return null; } catch (Exception e) { Console.WriteLine("Failed to send request"); Console.WriteLine(e); return null; } } private static void GetAuthorization(Request request) { try { // Flattens parameters in queryParam that have List or Map values. request.QueryParam = FlattenDictionary(request.QueryParam); // Step 1: Create a canonical request string StringBuilder canonicalQueryString = new(); foreach (var entry in request.QueryParam.OrderBy(e => e.Key.ToLower())) { if (canonicalQueryString.Length > 0) { canonicalQueryString.Append('&'); } canonicalQueryString.Append($"{PercentCode(entry.Key)}={PercentCode(entry.Value?.ToString() ?? "")}"); } byte[] requestPayload = request.Body ?? Encoding.UTF8.GetBytes(""); string hashedRequestPayload = Sha256Hash(requestPayload); request.Headers["x-acs-content-sha256"] = hashedRequestPayload; if (!string.IsNullOrEmpty(SecurityToken)) { request.Headers["x-acs-security-token"] = SecurityToken; } StringBuilder canonicalHeaders = new(); StringBuilder signedHeadersSb = new(); foreach (var entry in request.Headers.OrderBy(e => e.Key.ToLower())) { if (entry.Key.StartsWith("x-acs-", StringComparison.CurrentCultureIgnoreCase) || entry.Key.Equals("host", StringComparison.OrdinalIgnoreCase) || entry.Key.Equals(ContentType, StringComparison.OrdinalIgnoreCase)) { string lowerKey = entry.Key.ToLower(); string value = (entry.Value?.ToString() ?? "").Trim(); canonicalHeaders.Append($"{lowerKey}:{value}\n"); signedHeadersSb.Append($"{lowerKey};"); } } string signedHeaders = signedHeadersSb.ToString().TrimEnd(';'); string canonicalRequest = $"{request.HttpMethod}\n{request.CanonicalUri}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{hashedRequestPayload}"; Console.WriteLine($"canonicalRequest:{canonicalRequest}"); // Step 2: Create the string to sign string hashedCanonicalRequest = Sha256Hash(Encoding.UTF8.GetBytes(canonicalRequest)); string stringToSign = $"{Algorithm}\n{hashedCanonicalRequest}"; Console.WriteLine($"stringToSign:{stringToSign}"); // Step 3: Calculate the signature string signature = HmacSha256(AccessKeySecret, stringToSign); // Step 4: Construct the Authorization header string authorization = $"{Algorithm} Credential={AccessKeyId},SignedHeaders={signedHeaders},Signature={signature}"; request.Headers["Authorization"] = authorization; Console.WriteLine($"authorization:{authorization}"); } catch (Exception ex) { Console.WriteLine("Failed to get authorization"); Console.WriteLine(ex.Message); } } private static string FormDataToString(Dictionary<string, object> formData) { Dictionary<string, object> tileMap = FlattenDictionary( formData); StringBuilder result = new StringBuilder(); bool first = true; string symbol = "&"; foreach (var entry in tileMap) { string value = entry.Value?.ToString() ?? ""; if (!string.IsNullOrEmpty(value)) { if (!first) { result.Append(symbol); } first = false; result.Append(PercentCode(entry.Key)); result.Append("="); result.Append(PercentCode(value)); } } return result.ToString(); } private static Dictionary<string, object> FlattenDictionary(Dictionary<string, object> dictionary, string prefix = "") { var result = new Dictionary<string, object>(); foreach (var kvp in dictionary) { string key = string.IsNullOrEmpty(prefix) ? kvp.Key : $"{prefix}.{kvp.Key}"; if (kvp.Value is Dictionary<string, object> nestedDict) { var nestedResult = FlattenDictionary(nestedDict, key); foreach (var nestedKvp in nestedResult) { result[nestedKvp.Key] = nestedKvp.Value; } } else if (kvp.Value is List<string> list) { for (int i = 0; i < list.Count; i++) { result[$"{key}.{i + 1}"] = list[i]; } } else { result[key] = kvp.Value; } } return result; } private static string HmacSha256(string key, string message) { using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key))) { byte[] hashMessage = hmac.ComputeHash(Encoding.UTF8.GetBytes(message)); return BitConverter.ToString(hashMessage).Replace("-", "").ToLower(); } } private static string Sha256Hash(byte[] input) { byte[] hashBytes = SHA256.HashData(input); return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); } private static string PercentCode(string str) { if (string.IsNullOrEmpty(str)) { throw new ArgumentException("Input string cannot be null or empty."); } return Uri.EscapeDataString(str).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~"); } } }

Rust example

Note

The example code runs in a rustc 1.82.0 environment. You may need to adjust the code based on your specific setup.

To run the Rust example, add the following dependencies to your Cargo.toml file.

[dependencies] serde = { version = "1.0" } serde_json = "1.0" rand = "0.8" base64 = "0.21" sha2 = "0.10" chrono = "0.4" hmac = "0.12" hex = "0.4" reqwest = { version = "0.11", features = ["json"] } tokio = { version = "1", features = ["full"] } percent-encoding = "2.1"
use core::str; use std::collections::{BTreeMap, HashMap}; use std::env; use std::time::{SystemTime, SystemTimeError}; use chrono::DateTime; use hmac::{Hmac, Mac}; use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode}; use rand::Rng; use serde_json::{json, Value}; use std::borrow::Cow; use reqwest::{ Client, header::{HeaderMap, HeaderValue}, Method, Response, StatusCode, }; use sha2::{Digest, Sha256}; use base64::engine::general_purpose::STANDARD; use base64::Engine; // Generate x-acs-date pub fn current_timestamp() -> Result<u64, SystemTimeError> { Ok(SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH)? .as_secs()) } // URL encoding pub fn percent_code(encode_str: &str) -> Cow<'_, str> { let encoded = utf8_percent_encode(encode_str, NON_ALPHANUMERIC) .to_string() .replace("+", "20%") .replace("%5F", "_") .replace("%2D", "-") .replace("%2E", ".") .replace("%7E", "~"); Cow::Owned(encoded) // Returns a Cow<str> that can hold a String or &str } fn flatten_target_ops( targets: Vec<HashMap<&str, &str>>, base_key: &str, ) -> Vec<(&'static str, &'static str)> { let mut result = Vec::new(); for (idx, item) in targets.iter().enumerate() { let prefix = format!("{}.{}", base_key, idx + 1); for (&k, &v) in item { let key = format!("{}.{}", prefix, k); let key_static: &'static str = Box::leak(key.into_boxed_str()); let value_static: &'static str = Box::leak(v.to_string().into_boxed_str()); result.push((key_static, value_static)); } } result } /// Calculate SHA256 hash pub fn sha256_hex(message: &str) -> String { let mut hasher = Sha256::new(); hasher.update(message); format!("{:x}", hasher.finalize()).to_lowercase() } // HMAC SHA256 pub fn hmac256(key: &[u8], message: &str) -> Result<Vec<u8>, String> { let mut mac = Hmac::<Sha256>::new_from_slice(key) .map_err(|e| format!("use data key on sha256 fail:{}", e))?; mac.update(message.as_bytes()); let signature = mac.finalize(); Ok(signature.into_bytes().to_vec()) } // Generate a unique random number for the signature pub fn generate_random_string(length: usize) -> String { const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; let mut rng = rand::thread_rng(); (0..length) .map(|_| CHARSET[rng.gen_range(0..CHARSET.len())] as char) .collect() } pub fn generate_nonce() -> String { generate_random_string(32) } // Build a sorted and encoded canonical query string pub fn build_sored_encoded_query_string(query_params: &[(&str, &str)]) -> String { let sorted_query_params: BTreeMap<_, _> = query_params.iter().copied().collect(); let encoded_params: Vec<String> = sorted_query_params .into_iter() .map(|(k, v)| { let encoded_key = percent_code(k); let encoded_value = percent_code(v); format!("{}={}", encoded_key, encoded_value) }) .collect(); encoded_params.join("&") } // Read the response pub async fn read_response(result: Response) -> Result<(StatusCode, String), String> { let status = result.status(); let data = result.bytes().await.map_err(|e| format!("Read response body failed: {}", e))?; let res = match str::from_utf8(&data) { Ok(s) => s.to_string(), Err(_) => return Err("Body contains non UTF-8 characters".to_string()), }; Ok((status, res)) } // Define the value type for FormData #[derive(Debug, Clone)] pub enum FormValue { String(String), Vec(Vec<String>), HashMap(HashMap<String, String>), } // Define a request body enum to handle different request body types, including Json, Binary, and FormData. pub enum RequestBody { Json(HashMap<String, Value>), // Json Binary(Vec<u8>), // Binary FormData(HashMap<String, FormValue>), // FormData None, } // Canonical request pub async fn call_api( client: Client, method: Method, host: &str, canonical_uri: &str, query_params: &[(&str, &str)], action: &str, version: &str, body: RequestBody, access_key_id: &str, access_key_secret: &str, ) -> Result<String, String> { // Process the request body content based on the body type and store the result in the body_content variable. let body_content = match &body { RequestBody::Json(body_map) => json!(body_map).to_string(), RequestBody::Binary(binary_data) => { STANDARD.encode(binary_data) }, RequestBody::FormData(form_data) => { let params: Vec<String> = form_data .iter() .flat_map(|(k, v)| { match v { FormValue::String(s) => { vec![format!("{}={}", percent_code(k), percent_code(&s))] }, FormValue::Vec(vec) => { vec.iter() .map(|s| format!("{}={}", percent_code(k), percent_code(s))) .collect::<Vec<_>>() }, FormValue::HashMap(map) => { map.iter() .map(|(sk, sv)| format!("{}={}", percent_code(sk), percent_code(sv))) .collect::<Vec<_>>() }, } }) .collect(); params.join("&") }, RequestBody::None => String::new(), }; // Calculate x-acs-content-sha256 for the request body. Prepare x-acs-date, x-acs-signature-nonce, and the request headers to be signed. let hashed_request_payload = if body_content.is_empty() { sha256_hex("") } else { sha256_hex(&body_content) }; // x-acs-date let now_time = current_timestamp().map_err(|e| format!("Get current timestamp failed: {}", e))?; let datetime = DateTime::from_timestamp(now_time as i64, 0).ok_or_else(|| format!("Get datetime from timestamp failed: {}", now_time))?; let datetime_str = datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string(); // x-acs-signature-nonce let signature_nonce = generate_nonce(); println!("Signature Nonce: {}", signature_nonce); // Request headers to be signed let sign_header_arr = &[ "host", "x-acs-action", "x-acs-content-sha256", "x-acs-date", "x-acs-signature-nonce", "x-acs-version", ]; let sign_headers = sign_header_arr.join(";"); // 1. Create canonical request headers let mut headers = HeaderMap::new(); headers.insert("Host", HeaderValue::from_str(host).unwrap()); headers.insert("x-acs-action", HeaderValue::from_str(action).unwrap()); headers.insert("x-acs-version", HeaderValue::from_str(version).unwrap()); headers.insert("x-acs-date", HeaderValue::from_str(&datetime_str).unwrap()); headers.insert("x-acs-signature-nonce", HeaderValue::from_str(&signature_nonce).unwrap()); headers.insert("x-acs-content-sha256", HeaderValue::from_str(&hashed_request_payload).unwrap()); // 2. Create the request headers to be signed let canonical_query_string = build_sored_encoded_query_string(query_params); // Encode and concatenate parameters println!("CanonicalQueryString: {}", canonical_query_string); let canonical_request = format!( "{}\n{}\n{}\n{}\n\n{}\n{}", method.as_str(), canonical_uri, canonical_query_string, sign_header_arr.iter().map(|&header| format!("{}:{}", header, headers[header].to_str().unwrap())).collect::<Vec<_>>().join("\n"), sign_headers, hashed_request_payload ); println!("Canonical Request: {}", canonical_request); // 3. Calculate the SHA-256 hash of the request headers to be signed let result = sha256_hex(&canonical_request); // 4. Create the string to sign let string_to_sign = format!("ACS3-HMAC-SHA256\n{}", result); // 5. Calculate the signature let signature = hmac256(access_key_secret.as_bytes(), &string_to_sign)?; let data_sign = hex::encode(&signature); let auth_data = format!( "ACS3-HMAC-SHA256 Credential={},SignedHeaders={},Signature={}", access_key_id, sign_headers, data_sign ); // 6. Construct the Authorization header headers.insert("Authorization", HeaderValue::from_str(&auth_data).unwrap()); // Construct the URL and append request parameters let url: String; if !query_params.is_empty() { url = format!("https://{}{}?{}", host, canonical_uri,canonical_query_string); } else { url = format!("https://{}{}", host, canonical_uri); } // Call the function to send the request let response = send_request( &client, method, &url, headers, query_params, &body, &body_content, ) .await?; // Read the response let (_, res) = read_response(response).await?; Ok(res) } /// Send the request async fn send_request( client: &Client, method: Method, url: &str, headers: HeaderMap, query_params: &[(&str, &str)], // Receive query parameters body: &RequestBody, // Used to determine the body data type body_content: &str, // Receive body request parameters (FormData/Json/Binary) when the body is not empty ) -> Result<Response, String> { let mut request_builder = client.request(method.clone(), url); // Add request headers for (k, v) in headers.iter() { request_builder = request_builder.header(k, v.clone()); } // Add the request body match body { RequestBody::Binary(_) => { request_builder = request_builder.header("Content-Type", "application/octet-stream"); request_builder = request_builder.body(body_content.to_string()); // Move the value here } RequestBody::Json(_) => { // If the body is a map and not empty, convert it to JSON, store it in the body_content variable, and set application/json; charset=utf-8. if !body_content.is_empty() { request_builder = request_builder.body(body_content.to_string()); request_builder = request_builder.header("Content-Type", "application/json; charset=utf-8"); } } RequestBody::FormData(_) => { // Process form-data type and set the content-type if !body_content.is_empty() { request_builder = request_builder.header("Content-Type", "application/x-www-form-urlencoded"); request_builder = request_builder.body(body_content.to_string()); } } RequestBody::None => { request_builder = request_builder.body(String::new()); } } // Build the request let request = request_builder .build() .map_err(|e| format!("build request fail: {}", e))?; // Send the request let response = client .execute(request) .await .map_err(|e| format!("execute request fail: {}", e))?; // Return the result Ok(response) } /** * * Signature example. You need to replace the example parameters in the main method based on your actual requirements. * <p> * Obtain the request method (methods), request parameter name (name), parameter type (type), and parameter location (in) from the API metadata. * 1. If a request parameter is specified as "in":"query" in the metadata, pass it as a query_params. Note: For RPC APIs, this type of parameter can also be passed in the body with content-type set to application/x-www-form-urlencoded. See Example 3. * 2. If a request parameter is specified as "in": "body" in the metadata, pass it in the body. The MIME type can be application/octet-stream or application/json. For RPC APIs, we do not recommend using application/json. You can use the method in Example 3 instead. * 3. If a request parameter is specified as "in": "formData" in the metadata, pass it in the body. The MIME type is application/x-www-form-urlencoded. */ #[tokio::main] async fn main() { // Create an HTTP client let client = Client::new(); // env::var() is used to obtain the AccessKey ID and AccessKey secret from environment variables. let access_key_id = env::var("ALIBABA_CLOUD_ACCESS_KEY_ID").expect("Cannot get access key id."); let access_key_secret = env::var("ALIBABA_CLOUD_ACCESS_KEY_SECRET").expect("Cannot get access key id."); let access_key_id: &str = &access_key_id; let access_key_secret: &str = &access_key_secret; // RPC API request example 1: Request parameter is "in":"query" POST let method = Method::POST; // Request method let host = "ecs.cn-hangzhou.aliyuncs.com"; // Endpoint let canonical_uri = "/"; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. let action = "DescribeInstanceStatus"; // API name let version = "2014-05-26"; // API version number let region_id = "cn-hangzhou"; let instance_ids = vec![ "i-bp11ht4XXXXXXXX", "i-bp16mazXXXXXXXX", ]; let mut query: Vec<(&str, &str)> = Vec::new(); query.push(("RegionId", region_id)); for (index, instance_id) in instance_ids.iter().enumerate() { let key = format!("InstanceId.{}", index + 1); query.push((Box::leak(key.into_boxed_str()), instance_id)); } // Query parameters let query_params: &[(&str, &str)] = &query; // When the request body is empty let body = RequestBody:: None; // RPC API "in":"query" with complex query parameters POST // let method = Method::POST; // Request method // let host = "tds.cn-shanghai.aliyuncs.com"; // Endpoint // let canonical_uri = "/"; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. // let action = "AddAssetSelectionCriteria"; // API name // let version = "2018-12-03"; // API version number // Define parameters // let mut target_op = HashMap::new(); // target_op.insert("Operation", "add"); // target_op.insert("Target", "i-2ze1j7ocdXXXXXXXX"); // Define the TargetOperationList parameter, passing a map type in the collection // let target_operation_list = vec![target_op]; // Flatten parameters // let mut query = flatten_target_ops(target_operation_list, "TargetOperationList"); // Normal parameters // query.push(("SelectionKey", "85a561b7-27d5-47ad-a0ec-XXXXXXXX")); // let query_params: &[(&str, &str)] = &query; // let body = RequestBody:: None; // RPC API request example 2: Request parameter is "in":"body" (file upload scenario) POST // let method = Method::POST; // Request method // let host = "ocr-api.cn-hangzhou.aliyuncs.com"; // let canonical_uri = "/"; // let action = "RecognizeGeneral"; // let version = "2021-07-07"; // Request parameter "in":"body" with binary file type // let binary_data = std::fs::read("<FILE_PATH>").expect("Failed to read file"); // Replace <FILE_PATH> with the actual file path // When the body is of binary type // let body = RequestBody::Binary(binary_data); // Query parameters are empty // let query_params = &[]; // RPC API request example 3: Request parameter is "in": "formData" or "in":"body" (non-file upload scenario) POST // let method = Method::POST; // Request method // let host = "mt.aliyuncs.com"; // let canonical_uri = "/"; // let action = "TranslateGeneral"; // let version = "2018-10-12"; // // Parameters such as FormatType, SourceLanguage, and TargetLanguage are specified as "in":"formData" in the metadata. // let mut form_data = HashMap::new(); // Body type is FormData(HashMap<String, FormValue>). FormValue supports Vec<String>, HashSet<String>, or HashMap<String, String>. More types can be added in the FormValue enum. // form_data.insert(String::from("FormatType"),FormValue::String(String::from("text"))); // form_data.insert(String::from("SourceLanguage"),FormValue::String(String::from("zh"))); // form_data.insert(String::from("TargetLanguage"),FormValue::String(String::from("en"))); // form_data.insert(String::from("SourceText"),FormValue::String(String::from("Hello"))); // form_data.insert(String::from("Scene"),FormValue::String(String::from("general"))); // // Query parameters // let query_params = &[("Context", "Morning")]; // // When body is of FormData type, "in":"formdata" // let body = RequestBody::FormData(form_data); // ROA API POST request API: CreateCluster // Define API request constants // let method = Method::POST; // Request method // let host = "cs.cn-hangzhou.aliyuncs.com"; // let canonical_uri = "/clusters"; // let action = "CreateCluster"; // let version = "2015-12-15"; // Set request body parameters // let mut body_json = HashMap::new(); // Body type is Json(HashMap<String, Value>). Supported Value types: Value::String("test".to_string()) // String, Value::Number(serde_json::Number::from(42)) // Number, Value::Bool(true) // Boolean, Value::Null // Null, Value::Array(vec![Value::from(1), Value::from(2), Value::from(3)]) // Array, json!({"nested_key": "nested_value"}) // body_json.insert(String::from("name"),json!("Test Cluster")); // body_json.insert(String::from("region_id"),json!("cn-hangzhou")); // body_json.insert(String::from("cluster_type"),json!("ExternalKubernetes")); // body_json.insert(String::from("vpcid"),json!("vpc-2zeou1uodXXXXXXXX")); // body_json.insert(String::from("container_cidr"),json!("10.X.X.X/X")); // body_json.insert(String::from("service_cidr"),json!("10.X.X.X/X")); // body_json.insert(String::from("security_group_id"),json!("sg-2ze1a0rlgXXXXXXXX")); // body_json.insert( // String::from("vswitch_ids"), // Value::Array(vec![ // Value::from("vsw-2zei30dhflXXXXXXXX"), // Value::from("vsw-2zei30dhflXXXXXXXX"), // Value::from("vsw-2zei30dhflXXXXXXXX"), // ]), // ); // // Query parameters are empty // let query_params = &[]; // // When body is of Json type // let body = RequestBody::Json(body_json); // ROA API GET request API: DeleteCluster Query resources associated with a specified cluster // let method = Method::GET; // Request method // let host = "cs.cn-hangzhou.aliyuncs.com"; // Endpoint // // Concatenate the resource path // let uri = format!("/clusters/{}/resources", percent_code("ce196d21571a64be9XXXXXXXX").as_ref()); // let canonical_uri = uri.as_str(); // Convert the resource path to &str type // let action = "DescribeClusterResources"; // API name // let version = "2015-12-15"; // API version number // // Set query parameters // let query_params = &[("with_addon_resources", if true { "true" } else { "false" })]; // "true" or "false" // // Set body parameters to empty // let body = RequestBody:: None; // ROA API DELETE request API: DeleteCluster DELETE request to delete a pay-as-you-go cluster // let method = Method::DELETE; // let host = "cs.cn-hangzhou.aliyuncs.com"; // let uri = format!("/clusters/{}", percent_code("ce0138ff31ad044f8XXXXXXXX").as_ref()); // let canonical_uri = uri.as_str(); // Convert the resource path to &str type // let action = "DeleteCluster"; // let version = "2015-12-15"; // // Query parameters // let query_params = &[]; // // When body parameters are empty // let body = RequestBody:: None; // SendSms API // let method = Method::POST; // Request method // let host = "dysmsapi.aliyuncs.com"; // Endpoint // let canonical_uri = "/"; // RPC APIs have no resource path, so use a forward slash (/) as the CanonicalURI. // let action = "SendSms"; // API name // let version = "2017-05-25"; // API version number // let mut query: Vec<(&str, &str)> = Vec::new(); // query.push(("PhoneNumbers", "<YOUR_PHONENUMBERS>")); // query.push(("TemplateCode", "<YOUR_TEMPLATECODE>")); // query.push(("SignName", "<YOUR_SIGNNAME>")); // query.push(("TemplateParam", "<YOUR_TEMPLATEPARAM>")); // // Query parameters // let query_params: &[(&str, &str)] = &query; // // When the request body is empty // let body = RequestBody:: None; // Send the request match call_api( client.clone(), method, // API request method: POST/GET/DELETE host, // API endpoint canonical_uri, // API resource path query_params, // "in":"query" query parameters action, // API name version, // API version number body, // "in":"body" request body parameters. Supports Json/FormData/Binary types. access_key_id, access_key_secret, ) .await { Ok(response) => println!("Response: {}", response), Err(error) => eprintln!("Exception: {}", error), } } 

Shell script example

#!/bin/bash accessKey_id="<YOUR-ACCESSKEY-ID>" accessKey_secret="<YOUR-ACCESSKEY-SECRET>" algorithm="ACS3-HMAC-SHA256" # Request parameters -- This part needs to be modified based on your actual requirements. httpMethod="POST" host="dns.aliyuncs.com" queryParam=("DomainName=example.com" "RRKeyWord=@") action="DescribeDomainRecords" version="2015-01-09" canonicalURI="/" # Pass body-type or formdata-type parameters through the body. # For body-type parameters with a JSON string: body value is a JSON string like "{'key1':'value1','key2':'value2'}", and you must add content-type:application/json; charset=utf-8 to the signature header. # For body-type parameters with a binary file: no need to modify the body, just add content-type:application/octet-stream to the signature header and add the --data-binary parameter to the curl_command. # For formdata-type parameters: body parameter format is "key1=value1&key2=value2", and you must add content-type:application/x-www-form-urlencoded to the signature header. body="" # The UTC time in ISO 8601 format utc_timestamp=$(date +%s) utc_date=$(date -u -d @${utc_timestamp} +"%Y-%m-%dT%H:%M:%SZ") # x-acs-signature-nonce random number random=$(uuidgen | sed 's/-//g') # Signature header headers="host:${host} x-acs-action:${action} x-acs-version:${version} x-acs-date:${utc_date} x-acs-signature-nonce:${random}" # URL encoding function urlencode() { local string="${1}" local strlen=${#string} local encoded="" local pos c o for (( pos=0 ; pos<strlen ; pos++ )); do c=${string:$pos:1} case "$c" in [-_.~a-zA-Z0-9] ) o="${c}" ;; * ) printf -v o '%%%02X' "'$c" esac encoded+="${o}" done echo "${encoded}" } # Step 1: Create a canonical request string # Flatten all parameters in queryParam newQueryParam=() # Iterate through each original parameter for param in "${queryParam[@]}"; do # Check if it contains an equal sign to determine if it is a key-value pair if [[ "$param" == *"="* ]]; then # Split the key and value IFS='=' read -r key value <<< "$param" # URL-encode the value value=$(urlencode "$value") # Check if the value is a list (by looking for parentheses) if [[ "$value" =~ ^\(.+\)$ ]]; then # Remove the parentheses value="${value:1:-1}" # Use IFS to split the value list IFS=' ' read -ra values <<< "$value" # Add an index to each value index=1 for val in "${values[@]}"; do # Remove double quotes val="${val%\"}" val="${val#\"}" # Add to the new array newQueryParam+=("$key.$index=$val") ((index++)) done else # If it is not a list, add it directly newQueryParam+=("$key=$value") fi else # If there is no equal sign, keep it as is newQueryParam+=("$param") fi done # Process and sort the new query parameters sortedParams=() declare -A paramsMap for param in "${newQueryParam[@]}"; do IFS='=' read -r key value <<< "$param" paramsMap["$key"]="$value" done # Sort by key for key in $(echo ${!paramsMap[@]} | tr ' ' '\n' | sort); do sortedParams+=("$key=${paramsMap[$key]}") done # 1.1 Construct the canonical query string canonicalQueryString="" first=true for item in "${sortedParams[@]}"; do [ "$first" = true ] && first=false || canonicalQueryString+="&" # Check if an equal sign exists if [[ "$item" == *=* ]]; then canonicalQueryString+="$item" else canonicalQueryString+="$item=" fi done # 1.2 Process the request body hashedRequestPayload=$(echo -n "$body" | openssl dgst -sha256 | awk '{print $2}') headers="${headers} x-acs-content-sha256:$hashedRequestPayload" # 1.3 Construct the canonical request headers canonicalHeaders=$(echo "$headers" | grep -E '^(host|content-type|x-acs-)' | while read line; do key=$(echo "$line" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]') value=$(echo "$line" | cut -d':' -f2-) echo "${key}:${value}" done | sort | tr '\n' '\n') signedHeaders=$(echo "$headers" | grep -E '^(host|content-type|x-acs-)' | while read line; do key=$(echo "$line" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]') echo "$key" done | sort | tr '\n' ';' | sed 's/;$//') # 1.4 Construct the canonical request canonicalRequest="${httpMethod}\n${canonicalURI}\n${canonicalQueryString}\n${canonicalHeaders}\n\n${signedHeaders}\n${hashedRequestPayload}" echo -e "canonicalRequest=${canonicalRequest}" echo "+++++++++++++++++++++++++++++++++++++++++++++++++++" str=$(echo "$canonicalRequest" | sed 's/%/%%/g') hashedCanonicalRequest=$(printf "${str}" | openssl sha256 -hex | awk '{print $2}') # Step 2: Create the string to sign stringToSign="${algorithm}\n${hashedCanonicalRequest}" echo -e "stringToSign=$stringToSign" echo "+++++++++++++++++++++++++++++++++++++++++++++++++++" # Step 3: Calculate the signature signature=$(printf "${stringToSign}" | openssl dgst -sha256 -hmac "${accessKey_secret}" | sed 's/^.* //') echo -e "signature=${signature}" echo "+++++++++++++++++++++++++++++++++++++++++++++++++++" # Step 4: Construct the Authorization header authorization="${algorithm} Credential=${accessKey_id},SignedHeaders=${signedHeaders},Signature=${signature}" echo -e "authorization=${authorization}" # Construct the curl command url="https://$host$canonicalURI" curl_command="curl -X $httpMethod '$url?$canonicalQueryString'" # Add request headers IFS=$'\n' # Set the newline character as the new IFS for header in $headers; do curl_command="$curl_command -H '$header'" done curl_command+=" -H 'Authorization:$authorization'" # If the body-type parameter is a binary file, comment out the following line. curl_command+=" -d '$body'" # If the body-type parameter is a binary file, uncomment the following line. #curl_command+=" --data-binary @"/root/001.png" " echo "$curl_command" # Execute the curl command eval "$curl_command"

C language example

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <stdarg.h> #include <stdint.h> #include <openssl/hmac.h> #include <openssl/evp.h> #include <openssl/sha.h> #include <openssl/rand.h> #include <curl/curl.h> // getenv() is used to obtain the AccessKey ID and AccessKey secret from environment variables. #define ACCESS_KEY_ID getenv("ALIBABA_CLOUD_ACCESS_KEY_ID") #define ACCESS_KEY_SECRET getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET") #define ALGORITHM "ACS3-HMAC-SHA256" #define BUFFER_SIZE 4096 // Struct for sorting typedef struct { char key[256]; char value[256]; } KeyValuePair; // Comparison function: sort by key in alphabetical order int compare_pairs(const void *a, const void *b) { return strcmp(((const KeyValuePair *)a)->key, ((const KeyValuePair *)b)->key); } // URL encoding char* percentEncode(const char* str) { if (str == NULL) { fprintf(stderr, "Input string cannot be null\n"); return NULL; } size_t len = strlen(str); char* encoded = (char*)malloc(len * 3 + 1); if (encoded == NULL) { fprintf(stderr, "Memory allocation failed\n"); free(encoded); return NULL; } char* ptr = encoded; for (size_t i = 0; i < len; i++) { unsigned char c = (unsigned char)str[i]; if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { *ptr++ = c; } else { ptr += sprintf(ptr, "%%%02X", c); } } *ptr = '\0'; char* finalEncoded = malloc(strlen(encoded) + 1); if (finalEncoded) { char* fptr = finalEncoded; for (size_t j = 0; j < strlen(encoded); j++) { if (encoded[j] == '+') { strcpy(fptr, "%20"); fptr += 3; } else if (encoded[j] == '*') { strcpy(fptr, "%2A"); fptr += 3; } else if (encoded[j] == '~') { *fptr++ = '~'; } else { *fptr++ = encoded[j]; } } *fptr = '\0'; } free(encoded); return finalEncoded; } /** * @brief URL-encodes query parameters, sorts them alphabetically, and generates a canonical query string. * @param query_params The original query parameter string (e.g., "key1=value1&key2=value2"). * @return char* The sorted and encoded canonical query string (the caller must free the memory). */ char* generate_sorted_encoded_query(const char* query_params) { if (query_params == NULL || strlen(query_params) == 0) { return strdup(""); // Return an empty string for empty parameters } KeyValuePair pairs[100]; // Supports up to 100 key-value pairs int pair_count = 0; char* copy = strdup(query_params); if (!copy) { fprintf(stderr, "Memory allocation failed\n"); return NULL; } char* token = NULL; char* saveptr = NULL; token = strtok_r(copy, "&", &saveptr); while (token != NULL && pair_count < 100) { char* eq = strchr(token, '='); if (eq) { size_t key_len = eq - token; char key[256], value[256]; strncpy(key, token, key_len); key[key_len] = '\0'; const char* val = eq + 1; strncpy(value, val, sizeof(value) - 1); value[sizeof(value) - 1] = '\0'; char* encoded_key = percentEncode(key); char* encoded_value = percentEncode(value); strncpy(pairs[pair_count].key, encoded_key, sizeof(pairs[pair_count].key)); strncpy(pairs[pair_count].value, encoded_value, sizeof(pairs[pair_count].value)); pair_count++; free(encoded_key); free(encoded_value); } token = strtok_r(NULL, "&", &saveptr); } free(copy); // Sort by key qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs); // Concatenate the sorted query string char* query_sorted = malloc(BUFFER_SIZE); if (!query_sorted) { fprintf(stderr, "Memory allocation failed\n"); return NULL; } query_sorted[0] = '\0'; for (int i = 0; i < pair_count; ++i) { if (i == 0) { snprintf(query_sorted, BUFFER_SIZE, "%s=%s", pairs[i].key, pairs[i].value); } else { char temp[512]; snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value); strncat(query_sorted, temp, BUFFER_SIZE - strlen(query_sorted) - 1); } } return query_sorted; } // HMAC-SHA256 calculation void hmac256(const char *key, const char *message, char *output) { unsigned char hmac[SHA256_DIGEST_LENGTH]; unsigned int result_len; HMAC(EVP_sha256(), key, strlen(key), (unsigned char *)message, strlen(message), hmac, &result_len); for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { sprintf(output + (i * 2), "%02x", hmac[i]); } output[SHA256_DIGEST_LENGTH * 2] = '\0'; } // Calculate SHA-256 hash void sha256_hex(const char *input, char *output) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256((unsigned char *)input, strlen(input), hash); for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { sprintf(output + (i * 2), "%02x", hash[i]); } output[SHA256_DIGEST_LENGTH * 2] = '\0'; } // Used to generate x-acs-signature-nonce void generate_uuid(char *uuid, size_t size) { if (size < 37) { fprintf(stderr, "Buffer size too small for UUID\n"); return; } unsigned char random_bytes[16]; RAND_bytes(random_bytes, sizeof(random_bytes)); random_bytes[6] &= 0x0f; // Keep the high 4 bits random_bytes[6] |= 0x40; // Set version to 4 random_bytes[8] &= 0x3f; // Keep the high 2 bits random_bytes[8] |= 0x80; // Set variant to 10xx snprintf(uuid, size, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", random_bytes[0], random_bytes[1], random_bytes[2], random_bytes[3], random_bytes[4], random_bytes[5], random_bytes[6], random_bytes[7], random_bytes[8], random_bytes[9], random_bytes[10], random_bytes[11], random_bytes[12], random_bytes[13], random_bytes[14], random_bytes[15]); } // Upload a file size_t read_file(const char *file_path, char **buffer) { FILE *file = fopen(file_path, "rb"); if (!file) { fprintf(stderr, "Cannot open file %s\n", file_path); return 0; // Read failed } fseek(file, 0, SEEK_END); size_t file_size = ftell(file); fseek(file, 0, SEEK_SET); *buffer = (char *)malloc(file_size); if (!*buffer) { fprintf(stderr, "Failed to allocate memory for file buffer\n"); fclose(file); return 0; // Read failed } fread(*buffer, 1, file_size, file); fclose(file); return file_size; // Return the number of bytes read } // Calculate the Authorization header char* get_authorization(const char *http_method, const char *canonical_uri, const char *host, const char *x_acs_action, const char *x_acs_version, const char *query_params, const char *body, char *authorization_header, char *hashed_payload, char *x_acs_date, char *uuid) { // Prepare x-acs-signature-nonce, x-acs-date, x-acs-content-sha256, and the string to be signed. generate_uuid(uuid, 37); // x-acs-date format is yyyy-MM-ddTHH:mm:ssZ, for example, 2025-04-17T07:19:10Z. time_t now = time(NULL); struct tm *utc_time = gmtime(&now); strftime(x_acs_date, 64, "%Y-%m-%dT%H:%M:%SZ", utc_time); // String to be signed char signed_headers[] = "host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version"; // x-acs-content-sha256 sha256_hex(body ? body : "", hashed_payload); printf("Generated x-acs-content-sha256: %s\n", hashed_payload); // 1. Create canonical request headers char canonical_headers[BUFFER_SIZE]; snprintf(canonical_headers, sizeof(canonical_headers), "host:%s\nx-acs-action:%s\nx-acs-content-sha256:%s\nx-acs-date:%s\nx-acs-signature-nonce:%s\nx-acs-version:%s", host, x_acs_action, hashed_payload, x_acs_date, uuid, x_acs_version); printf("Canonical Headers:\n%s\n", canonical_headers); // 2. Create the request headers to be signed // Sort and encode the query parameters char* sorted_query_params = generate_sorted_encoded_query(query_params); if (!sorted_query_params) { fprintf(stderr, "Failed to generate sorted query string\n"); return NULL; } char canonical_request[BUFFER_SIZE]; snprintf(canonical_request, sizeof(canonical_request), "%s\n%s\n%s\n%s\n\n%s\n%s", http_method, canonical_uri, sorted_query_params ? sorted_query_params : "", canonical_headers, signed_headers, hashed_payload); printf("Canonical Request:\n%s\n", canonical_request); // 3. Calculate the SHA-256 hash of the canonical request char hashed_canonical_request[SHA256_DIGEST_LENGTH * 2 + 1]; sha256_hex(canonical_request, hashed_canonical_request); printf("hashedCanonicalRequest: %s\n", hashed_canonical_request); // 4. Create the string to sign char string_to_sign[BUFFER_SIZE]; snprintf(string_to_sign, sizeof(string_to_sign), "%s\n%s", ALGORITHM, hashed_canonical_request); printf("stringToSign:\n%s\n", string_to_sign); // 5. Calculate the signature char signature[SHA256_DIGEST_LENGTH * 2 + 1]; hmac256(ACCESS_KEY_SECRET, string_to_sign, signature); printf("Signature: %s\n", signature); // 6. Construct the Authorization header snprintf(authorization_header, BUFFER_SIZE, "%s Credential=%s,SignedHeaders=%s,Signature=%s", ALGORITHM, ACCESS_KEY_ID, signed_headers, signature); printf("Authorization: %s\n", authorization_header); return sorted_query_params; } // Send the request void call_api(const char *http_method, const char *canonical_uri, const char *host, const char *x_acs_action, const char *x_acs_version, const char *query_params, const char *body,const char *content_type, size_t body_length) { // Get the parameter values required for signature calculation char authorization_header[BUFFER_SIZE]; char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1]; char x_acs_date[64]; char uuid[37]; // 1. Initialize curl CURL *curl = curl_easy_init(); if (!curl) { fprintf(stderr, "curl_easy_init() failed\n"); goto cleanup; } // 2. Calculate the signature (returns sorted and encoded query parameters) char *signed_query_params = get_authorization(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, authorization_header, hashed_payload, x_acs_date, uuid); // 3. Add request parameters char url[BUFFER_SIZE]; if (signed_query_params && strlen(signed_query_params) > 0) { snprintf(url, sizeof(url), "https://%s%s?%s", host, canonical_uri, signed_query_params); } else { snprintf(url, sizeof(url), "https://%s%s", host, canonical_uri); } printf("Request URL: %s\n", url); // Free memory if (signed_query_params) { free(signed_query_params); // Free memory } // 4. Add request headers struct curl_slist *headers = NULL; char header_value[BUFFER_SIZE]; snprintf(header_value, sizeof(header_value), "Content-Type: %s", content_type); headers = curl_slist_append(headers, header_value); snprintf(header_value, sizeof(header_value), "Authorization: %s", authorization_header); headers = curl_slist_append(headers, header_value); snprintf(header_value, sizeof(header_value), "host: %s", host); headers = curl_slist_append(headers, header_value); snprintf(header_value, sizeof(header_value), "x-acs-action: %s", x_acs_action); headers = curl_slist_append(headers, header_value); snprintf(header_value, sizeof(header_value), "x-acs-content-sha256: %s", hashed_payload); headers = curl_slist_append(headers, header_value); snprintf(header_value, sizeof(header_value), "x-acs-date: %s", x_acs_date); headers = curl_slist_append(headers, header_value); snprintf(header_value, sizeof(header_value), "x-acs-signature-nonce: %s", uuid); headers = curl_slist_append(headers, header_value); snprintf(header_value, sizeof(header_value), "x-acs-version: %s", x_acs_version); headers = curl_slist_append(headers, header_value); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, http_method); curl_easy_setopt(curl, CURLOPT_URL, url); // Other CURL settings: disable SSL verification, add debugging information curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // 5. Add the request body if (body) { curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body_length); if (strcmp(content_type, "application/octet-stream") == 0) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); } else if (strcmp(content_type, "application/x-www-form-urlencoded") == 0) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); } else if (strcmp(content_type, "application/json; charset=utf-8") == 0) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); } } printf("RequestBody:%s\n",body); // 6. Send the request CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); goto cleanup; } cleanup: if (headers) curl_slist_free_all(headers); if (curl) curl_easy_cleanup(curl); } /** * * Signature example. You need to replace the example parameters in the main method based on your actual requirements. * <p> * Obtain the request method (methods), request parameter name (name), parameter type (type), and parameter location (in) from the API metadata. * 1. If a request parameter is specified as "in":"query" in the metadata, pass it as a query_params. Note: For RPC APIs, this type of parameter can also be passed in the body with content-type set to application/x-www-form-urlencoded. See Example 3. * 2. If a request parameter is specified as "in": "body" in the metadata, pass it in the body. The MIME type can be application/octet-stream or application/json. For RPC APIs, we do not recommend using application/json. You can use the method in Example 3 instead. * 3. If a request parameter is specified as "in": "formData" in the metadata, pass it in the body. The MIME type is application/x-www-form-urlencoded. */ int main() { // Set the response format to UTF-8 SetConsoleOutputCP(CP_UTF8); srand((unsigned int)time(NULL)); /** * RPC API request example: Request parameter is "in":"query" with a complex type. */ const char *http_method = "POST"; const char *canonical_uri = "/"; const char *host = "tds.cn-shanghai.aliyuncs.com"; const char *x_acs_action = "AddAssetSelectionCriteria"; const char *x_acs_version = "2018-12-03"; // Define the SelectionKey parameter of string type const char *selection_key = "85a561b7-27d5-47ad-a0ec-XXXXXXXX"; // Define the TargetOperationList parameter, a collection of target objects (can be extended with more) struct { const char *operation; const char *target; } targetOperation_list[] = { {"add", "i-2ze1j7ocdg9XXXXXXXX"}, // You can add more entries // {"add", "i-abc123xyzXXXXX"}, }; int count = sizeof(targetOperation_list) / sizeof(targetOperation_list[0]); KeyValuePair pairs[100]; // Store original keys and values int pair_count = 0; for (int i = 0; i < count; ++i) { char op_key[128], target_key[128]; snprintf(op_key, sizeof(op_key), "TargetOperationList.%d.Operation", i + 1); snprintf(target_key, sizeof(target_key), "TargetOperationList.%d.Target", i + 1); strncpy(pairs[pair_count].key, op_key, sizeof(pairs[pair_count].key)); strncpy(pairs[pair_count].value, targetOperation_list[i].operation, sizeof(pairs[pair_count].value)); pair_count++; strncpy(pairs[pair_count].key, target_key, sizeof(pairs[pair_count].key)); strncpy(pairs[pair_count].value, targetOperation_list[i].target, sizeof(pairs[pair_count].value)); pair_count++; } // Add the SelectionKey parameter snprintf(pairs[pair_count].key, sizeof(pairs[pair_count].key), "SelectionKey"); snprintf(pairs[pair_count].value, sizeof(pairs[pair_count].value), "%s", selection_key); pair_count++; // Sorting and encoding are done in get_authorization() qsort(pairs, pair_count, sizeof(KeyValuePair), compare_pairs); // Construct the original query string (unencoded) char query_params[BUFFER_SIZE] = {0}; for (int i = 0; i < pair_count; ++i) { if (i == 0) { snprintf(query_params, sizeof(query_params), "%s=%s", pairs[i].key, pairs[i].value); } else { char temp[512]; snprintf(temp, sizeof(temp), "&%s=%s", pairs[i].key, pairs[i].value); strncat(query_params, temp, sizeof(query_params) - strlen(query_params) - 1); } } const char *body = ""; // The request body is empty const char *content_type = "application/json; charset=utf-8"; call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body)); /** * RPC API request example: Request parameter is "in":"query" */ // Define API request parameters // const char *http_method = "POST"; // const char *canonical_uri = "/"; // const char *host = "ecs.cn-hangzhou.aliyuncs.com"; // const char *x_acs_action = "DescribeInstanceStatus"; // const char *x_acs_version = "2014-05-26"; // // Define the InstanceId parameter, an array. InstanceId is an optional parameter. // const char *instance_ids[] = { // "i-bp11ht4hXXXXXXXX", // "i-bp16maz3XXXXXXXX" // }; // // Concatenate the InstanceId array // char InstanceId[BUFFER_SIZE]; // snprintf(InstanceId, sizeof(InstanceId), // "InstanceId.1=%s&InstanceId.2=%s", // instance_ids[0], // instance_ids[1]); // // Define query parameters. Required parameter: RegionId=cn-hangzhou. const char *query_params = "RegionId=cn-hangzhou"; // char query_params[BUFFER_SIZE]; // snprintf(query_params, sizeof(query_params), // "%s&RegionId=cn-hangzhou", InstanceId); // const char *body = ""; // const char *content_type = "application/json; charset=utf-8"; // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body)); /** * RPC API request example: Request parameter is "in":"body" (file upload scenario) */ // Declare a pointer to store the read file content // char *body = NULL; // size_t body_length = read_file("<YOUR_FILE_PATH>", &body); // if (body_length > 0) { // const char *http_method = "POST"; // const char *canonical_uri = "/"; // const char *host = "ocr-api.cn-hangzhou.aliyuncs.com"; // const char *x_acs_action = "RecognizeGeneral"; // const char *x_acs_version = "2021-07-07"; // const char *query_params = ""; // const char *content_type = "application/octet-stream"; // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, body_length); // free(body); // } else { // fprintf(stderr, "File read error\n"); // } /** * RPC API request example: Request parameter is "in": "formData" or "in":"body" (non-file upload scenario) */ // const char *http_method = "POST"; // const char *canonical_uri = "/"; // const char *host = "mt.aliyuncs.com"; // const char *x_acs_action = "TranslateGeneral"; // const char *x_acs_version = "2018-10-12"; // char query_params[BUFFER_SIZE]; // snprintf(query_params, sizeof(query_params), "Context=%s", "Morning"); // const char *format_type = "text"; // const char *source_language = "zh"; // const char *target_language = "en"; // const char *source_text = "Hello"; // const char *scene = "general"; // char body[BUFFER_SIZE]; // snprintf(body, sizeof(body), // "FormatType=%s&SourceLanguage=%s&TargetLanguage=%s&SourceText=%s&Scene=%s", // percentEncode(format_type), percentEncode(source_language), percentEncode(target_language), // percentEncode(source_text), percentEncode(scene)); // const char *content_type = "application/x-www-form-urlencoded"; // printf("formdate_body: %s\n", body); // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body)); // RPC API request example 3: Request parameter is "in": "formData" // const char *http_method = "POST"; // const char *canonical_uri = "/"; // const char *host = "sasti.aliyuncs.com"; // const char *x_acs_action = "AskTextToTextMsg"; // const char *x_acs_version = "2020-05-12"; // // query // const char *query_params = ""; // // body // const char *Memory = "false"; // const char *Stream = "true"; // const char *ProductCode = "sddp_pre"; // const char *Feature = "{}"; // const char *Model = "yunsec-llm-latest"; // const char *Type = "Chat"; // const char *TopP = "0.9"; // const char *Temperature = "0.01"; // const char *Prompt = "Who are you"; // const char *Application = "sddp_pre"; // char body[BUFFER_SIZE]; // snprintf(body, sizeof(body), // "Memory=%s&Stream=%s&ProductCode=%s&Feature=%s&Model=%s&Type=%s&TopP=%s&Temperature=%s&Prompt=%s&Application=%s", // Memory, Stream, ProductCode, Feature, Model, Type, TopP, Temperature, Prompt, Application); // const char *content_type = "application/x-www-form-urlencoded"; // printf("formdate_body: %s\n", body); // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body)); /** * ROA API POST request with "in": "body" */ // const char *http_method = "POST"; // const char *canonical_uri = "/clusters"; // const char *host = "cs.cn-beijing.aliyuncs.com"; // const char *x_acs_action = "CreateCluster"; // const char *x_acs_version = "2015-12-15"; // const char *query_params = ""; // char body[BUFFER_SIZE]; // snprintf(body, sizeof(body), // "{\"name\":\"%s\",\"region_id\":\"%s\",\"cluster_type\":\"%s\"," // "\"vpcid\":\"%s\",\"container_cidr\":\"%s\"," // "\"service_cidr\":\"%s\",\"security_group_id\":\"%s\"," // "\"vswitch_ids\":[\"%s\"]}", // "Test Cluster", "cn-beijing", "ExternalKubernetes", // "vpc-2zeou1uod4yXXXXXXXX", "10.X.X.X/XX", // "10.X.X.X/XX", "sg-2ze1a0rlgeXXXXXXXX", // "vsw-2zei30dhflXXXXXXXX"); // const char *content_type = "application/json; charset=utf-8"; // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body)); /** * ROA API GET request */ // const char *http_method = "GET"; // char canonical_uri[BUFFER_SIZE]; // snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s/resources", percentEncode("cd1f5ba0dbfa144XXXXXXXX")); // const char *host = "cs.cn-beijing.aliyuncs.com"; // const char *x_acs_action = "DescribeClusterResources"; // const char *x_acs_version = "2015-12-15"; // const char *query_params = "with_addon_resources=true"; // const char *body = ""; // const char *content_type = ""; // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body)); /** * ROA API DELETE request */ // const char *http_method = "DELETE"; // char canonical_uri[BUFFER_SIZE]; // snprintf(canonical_uri, sizeof(canonical_uri), "/clusters/%s", percentEncode("cd1f5ba0dbfa144XXXXXXXX")); // const char *host = "cs.cn-beijing.aliyuncs.com"; // const char *x_acs_action = "DeleteCluster"; // const char *x_acs_version = "2015-12-15"; // const char *query_params = ""; // const char *body = ""; // const char *content_type = ""; // call_api(http_method, canonical_uri, host, x_acs_action, x_acs_version, query_params, body, content_type, strlen(body)); // Variables to store the generated values char authorization_header[BUFFER_SIZE]; char hashed_payload[SHA256_DIGEST_LENGTH * 2 + 1]; char x_acs_date[64]; char uuid[37]; return 0; } 

FAQ

Signature verification failed with the error message "Specified signature does not match our calculation." or "The request signature does not conform to Aliyun standards."

Cause:

Most issues occur when you create the CanonicalRequest. Common causes include the following:

  • Incorrect AccessKey pair configuration.

  • Incorrect parameter passing location. For example, passing query parameters in the request body.

  • The parameters in CanonicalQueryString are not sorted in ascending alphabetical order.

  • The SignedHeaders are not sorted in ascending alphabetical order.

  • Spaces are not encoded as %20.

  • An extra URL encoding was performed. During signature calculation, URL encoding should be performed only once when you process path parameters and the canonical query string. For example, multiple instances of %25 in the error message indicate that the % character was also encoded.

Solution:

Note

We recommend that you first check whether your local code calculates the correct result based on the Fixed parameter example.

  1. Compare the CanonicalRequest in the error message with the one calculated locally. If they are different, carefully check your code by referring to the common causes listed previously and the description in Step 1: Create a canonical request.

  2. If the CanonicalRequest is consistent, check whether the StringToSign in the error message is different from the one calculated locally. If it is, your hash algorithm may be incorrect.

  3. If the StringToSign is consistent, the AccessKey secret may be incorrect or the encryption algorithm may be incorrect.

  4. If the issue persists, contact us.

How do I test with Postman?

You cannot directly call OpenAPI using Postman. If you want to test with Postman, follow these steps:

  1. Calculate the Authorization value based on the signature mechanism using code or a script.

  2. Copy the request header information from CanonicalHeaders to the Headers section in Postman. Then, enter the Authorization information in the Headers section. Example:

    Key

    Example value

    host

    dysmsapi.aliyuncs.com

    x-acs-action

    SendSms

    x-acs-content-sha256

    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

    x-acs-date

    2025-04-16T07:45:55Z

    x-acs-signature-nonce

    315484d3-b129-4966-974a-699b7ee56647

    x-acs-version

    2017-05-25

    Authorization

    ACS3-HMAC-SHA256 Credential=testAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=b37aac99faa507472778256374366b7a47ba48adbc484a53ad789db194658a2d

  3. Set the parameters in Postman based on the parameter type:

    Note

    The parameter order must be the same as the order used for signature calculation.

    • For query parameters, enter them in the Params section.

    • For body parameters, enter them in the Body section.

How do I pass request parameters?

In the API metadata, the in field defines the location of each parameter, which determines how the parameter is passed.

Parameter location

Description

content-type

"in": "query"

Query parameters. They appear after the question mark (?) at the end of the request URL. Different name=value pairs are separated by ampersands (&).

Optional. If specified, the value must be application/json.

"in": "formData"

Form parameters. Concatenate the parameters into a string in the format key1=value1&key2=value2&key3=value3 and pass the string in the request body. If the parameter type is array or object, convert the value into indexed key-value pairs. For example, the object value {"key":["value1","value2"]} becomes {"key.1":"value1","key.2":"value2"}.

Required. The request Content-Type must be application/x-www-form-urlencoded.

"in": "body"

The entity parameter is in the request body.

Required. The value of content-type depends on the content type of the request. For example:

  • For JSON data, the value is application/json.

  • For a binary file stream, the value is application/octet-stream.

How to determine the style when the value of style in API metadata is not RPC or ROA

RPC or ROA only affects the value of CanonicalURI. If the value of the style parameter is a type other than RPC or ROA, check whether the path parameter has a value in the API metadata summary. If the path parameter has a value, the value of CanonicalURI is the value of `path`. If the parameter has no value, the value of CanonicalURI is a forward slash (/). For example, in the API metadata for querying a list of ACK clusters, the value of path is /api/v1/clusters. Therefore, the value of CanonicalURI is /api/v1/clusters.image

How to pass parameters for array and object types?

If a request parameter is a complex data structure, you must convert the parameter value to indexed key-value pairs.

Example 1: {"InstanceId":["i-bp10igfmnyttXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp1incuofvzxXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX","i-bp10igfmnyttXXXXXXXX"]} becomes:

{ "InstanceId.1": "i-bp10igfmnyttXXXXXXXX", "InstanceId.10": "i-bp10igfmnyttXXXXXXXX", "InstanceId.11": "i-bp10igfmnyttXXXXXXXX", "InstanceId.12": "i-bp10igfmnyttXXXXXXXX", "InstanceId.2": "i-bp1incuofvzxXXXXXXXX", "InstanceId.3": "i-bp1incuofvzxXXXXXXXX", "InstanceId.4": "i-bp10igfmnyttXXXXXXXX", "InstanceId.5": "i-bp10igfmnyttXXXXXXXX", "InstanceId.6": "i-bp10igfmnyttXXXXXXXX", "InstanceId.7": "i-bp10igfmnyttXXXXXXXX", "InstanceId.8": "i-bp10igfmnyttXXXXXXXX", "InstanceId.9": "i-bp10igfmnyttXXXXXXXX" }

Example 2: {"ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd","RegionId":"cn-shanghai","Tag":[{"tag1":"value1","tag2":"value2"}]} becomes:

{ "ImageId":"win2019_1809_x64_dtc_zh-cn_40G_alibase_20230811.vhd", "RegionId":"cn-shanghai", "Tag.1.tag1":"value1", "Tag.1.tag2":"value2" }

How to obtain the API version (x-acs-version)

  1. Go to the Alibaba Cloud OpenAPI Developer Portal. Select the cloud product for the API that you want to call, such as ECS.image

  2. Find the recommended API version on the product homepage. For example, the recommended API version for ECS is 2014-05-26.

    image

If a GET request with self-signing is successful, can I use POST?

  • RPC APIs usually support both GET and POST requests.

  • ROA APIs support only a single request method.

To find the request methods that an API supports, see the OpenAPI metadata.

The 'You are not authorized to do this operation.' error

Cause: The Resource Access Management (RAM) user for your AccessKey does not have permission to call the API.

Solution: For more information, see code 403, You are not authorized to do this operation. Action: xxxx.

How to obtain an AccessKey?

An AccessKey is a permanent access credential from Alibaba Cloud. It is a key pair that consists of an AccessKey ID and an AccessKey secret. When you call an API to access Alibaba Cloud resources, the system verifies the request by checking the AccessKey ID and a signature in the request. The signature is generated using the AccessKey secret. To obtain an AccessKey, see Create an AccessKey for a RAM user.

Contact us

If you cannot resolve an issue during signature calculation, join the DingTalk group at 147535001692. You can contact our on-duty staff for assistance.

Note

Do not join this group for issues unrelated to signature calculation, because you will not receive a valid response.