Overview
::: warning: Version notes This document describes the signature verification scheme for version 1.0. Recommend new projects use the simpler V2.0 version. :::
ArmCloud's server-side OpenAPI V1.0 signature verification is a comprehensive API authentication solution. Each request verifies your signature to ensure the security of your cloud phone instance information. Please take good care of your account's Access Key ID and Secret Access Key information (hereafter referred to as AK/SK).
Explanation of necessary development parameters
Each API request header must include the following parameters for authentication:
| Parameter name | Usage and access instructions |
|---|---|
| x-date | The timestamp for sending the request uses UTC time, accurate to seconds, e.g., 20240301T093700Z |
| x-host | Interface access domain name fixed value openapi-hk.armcloud.net |
| content-type | The MIME type of the resource is based on the actual interface specified value, such as: application/json |
| authorization | For specific generation rules, please refer to the [authorization Authorization Signature] (#authorization) for specific generation rules. example: HMAC-SHA256 Credential={AK}/{xDate}/armcloud-paas/request, SignedHeaders=content-type; host; x-content-sha256; x-date, Signature={signature} |
Authorization Signature
Authorization Signature Information Generation. To facilitate customer access, the official provider provides SDK-JAVA and SDK-PYTHON reference templates, which internally implement signature verification. By calling AuthorizationUtil.authorizationCreate, (AK, SK, request timestamp, static domain name, request message format, request message information, request type) is passed in. It automatically calculates and generates the authorization signature for requests.
[Reference for obtaining accounts (AK/SK)] (#get-the-account--ak-sk)
[Description of the AuthorizationUtil Signature Generation Class] (#instructions-for-the-authorizationutil-signature-generation-class)
[SDK-JAVA Reference Template] (#sdk-java-reference-template)
[SDK-PYTHON Reference Template] (#sdk-python-reference-template)
Obtaining Account (AK/SK)
Before starting to generate authorization signatures, you need to obtain the Access Key ID and Secret Access Key for API request authentication.
Steps: After logging in to the ARMCloud platform, User Management -> Account Information In the menu, appKey is AK, appSecret is SK.

AuthorizationUtil Signature Generation Class Description
Analysis of the SDK-JAVA Authorization.authorizationCreate signature generation method.
Class name: AuthorizationUtil
Authorization signature generation method: AuthorizationUtil.authorizationCreate(String AK, String SK, String xDate, String xHost, String contentTypeJson, Map<String,Object> requestBody, String httpMethodStr)
| Parameter name | Type | Example values | Parameter description |
|---|---|---|---|
| AK | String | xxxx | The Access Key ID of the account |
| SK | String | xxxx | Secret Access Key for the account |
| xDate | String | 20240301T093700Z | The requested timestamp uses UTC time, accurate to the second |
| xHost | String | openapi-hk.armcloud.net | Static domain name |
| contentTypeJson | String | application/json | Request message format |
| requestBody | Map<String,Object> | new Map<String,Object> | Request message information |
| httpMethodStr | String | POST | Request method: GET or POST |
/**
* Signature tool class
*/
public static class AuthorizationUtil {
private static final String service = "armcloud-paas"; //Fixed service name
private static final String algorithm = "HMAC-SHA256"; //Fixed signature algorithm
/**
* authorization generation tool method
*
* @param Access Key ID for the AK account
* @param Secret Access Key for SK accounts
* @param xDate follows the ISO 8601 standard time format: YYYYMMDD'T'HHMMSS'Z' for example: 20201103T104027Z
* @param xHost fixed domain
* @param contentTypeJson requests content formatting
* @param requestBody Requests JSON data
* @param httpMethodStr Request method: GET or POST
* @return authorization
* @throws Exception
*/
public static String authorizationCreate(String AK, String SK, String xDate, String xHost, String contentTypeJson
, Map<String,Object> requestBody, String httpMethodStr) throws Exception {
byte[] body = new byte[0];
if (requestBody != null) {
if ("GET".equals(httpMethodStr)) {
StringBuilder sb = new StringBuilder();
Iterator<String> keys = requestBody.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
sb.append(key).append("=").append(requestBody.get(key)).append("&");
}
// Remove the last '&' character (if present)
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
}
body = sb.toString().getBytes(StandardCharsets.UTF_8);
} else {
body = MapToJsonUtil.mapToJson(requestBody).getBytes(StandardCharsets.UTF_8);
}
}
final String xContentSha256 = hashSHA256(body);
//Short-term format
final String shortDate = xDate.substring(0, 8);
String signedHeaders = "content-type;host;x-content-sha256;x-date";
String canonicalStringBuilder =
"host:" + xHost + "\n" + "x-date:" + xDate + "\n" + "content-type:" + contentTypeJson + "\n"
+ "signedHeaders:" + signedHeaders + "\n" + "x-content-sha256:" + xContentSha256;
String hashCanonicalString = hashSHA256(canonicalStringBuilder.getBytes());
String credentialScope = shortDate + "/" + service + "/request";
String signString = algorithm + "\n" + xDate + "\n" + credentialScope + "\n" + hashCanonicalString;
byte[] signKey = genSigningSecretKeyV4(SK, shortDate, service);
String signature = bytesToHex(hmacSHA256(signKey, signString));
//authorization
String authorization = "HMAC-SHA256 Credential=" + AK + "/" + xDate + "/" + service
+ "/request, SignedHeaders=" + signedHeaders + ", Signature=" + signature;
return authorization;
}
private static byte[] genSigningSecretKeyV4(String secretKey, String shortXDate, String service) throws Exception {
byte[] kDate = hmacSHA256((secretKey).getBytes(), shortXDate);
byte[] kService = hmacSHA256(kDate, service);
return hmacSHA256(kService, "request");
}
private static String hashSHA256(byte[] content) throws Exception {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
return bytesToHex(md.digest(content));
} catch (Exception e) {
throw new Exception("Unable to compute hash while signing request: " + e.getMessage(), e);
}
}
private static String bytesToHex(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return "";
}
final StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
private static byte[] hmacSHA256(byte[] key, String content) throws Exception {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(content.getBytes());
} catch (Exception e) {
throw new Exception("Unable to calculate a request signature: " + e.getMessage(), e);
}
}
}
SDK-JAVA reference template
[Download SDK-JAVA] (https://oss-hk01.armcloud.net/arm-cloud-sdk/sdk_java.zip)
package net.armcloud.console.sdk.test;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* Signature testing class
*/
public class AuthorizationTest {
//GET Signature Request Test—POST Signature Request Test
public static void main(String[] args) throws Exception {
//Replace it with AK from your own account
final String AK = "xxxxxx";
//SK replaced with his own account
final String SK = "xxxx";
//Static domain name
final String xHost = "openapi-hk.armcloud.net";
//Request message format
final String contentType = "application/json";
//Query the supported callback type interface
selectList( AK, SK, xHost, contentType);
//Instance grouped list interface
groupInfos( AK, SK, xHost, contentType);
}
/**
* Query the supported callback type interface
* @param AK
* @param SK
* @param xHost
* @param contentType
* @throws Exception
*/
private static void selectList ( String AK, String SK, String xHost, String contentType) throws Exception {
String postUrlAddress = "https://"+xHost+"/openapi/open/config/selectList";
Map<String,Object> getRequestBody = new HashMap<>();
get(postUrlAddress, AK, SK, xHost, contentType, getRequestBody);
}
/**
* Instance grouped list interface
* @param AK
* @param SK
* @param xHost
* @param contentType
* @throws Exception
*/
private static void groupInfos ( String AK, String SK, String xHost, String contentType) throws Exception {
String postUrlAddress = "https://"+xHost+"/openapi/open/group/infos";
List<Integer> jsonArray = new ArrayList<>();
jsonArray.add(1);
Map<String,Object> postRequestBody = new HashMap<>();
postRequestBody.put("padCode", "AC32010180376");
postRequestBody.put("groupIds", jsonArray);
post(postUrlAddress, AK, SK, xHost, contentType, postRequestBody);
}
/**
* GET encapsulation request method
*
* @param urlAddress request address
* @param AK AK
* @param SK SK
* @param xHost fixed domain
* @param contentType Request packet format
* @param requestBody Requests packets
* @throws Exception
*/
public static String get(String urlAddress, String AK, String SK, String xHost, String contentType
, Map<String,Object> requestBody) throws Exception {
LocalDateTime localDateTime = LocalDateTime.now(); // At the current time
String xDate = localDateTimeToStringUTC(localDateTime); // UTC time format
URL url = new URL(urlAddress + getParamString(requestBody)); // Create a URL object
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // Open the connection
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000); // Set the connection timeout to 5 seconds
connection.setReadTimeout(5000); // Set the read timeout to 5 seconds
connection.setRequestProperty("content-type", contentType); // Set the request header
connection.setRequestProperty("x-host", xHost);
connection.setRequestProperty("x-date", xDate);
//SDK AuthorizationUtil: The authorizationCreate tool class uses the authorizationCreate method to obtain the authorization authentication interface
String authorization = AuthorizationUtil.authorizationCreate(AK, SK, xDate, xHost, contentType
, requestBody, "GET");
connection.setRequestProperty("authorization", authorization);
try {
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
System.out.println("GET Response Body: " + response);
return String.valueOf(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
connection.disconnect();
}
return null;
}
/**
* Post encapsulation request method
*
* @param urlAddress request address
* @param AK AK
* @param SK SK
* @param xHost fixed domain
* @param contentType Request packet format
* @param requestBody Requests packets
* @throws Exception
*/
public static String post(String urlAddress, String AK, String SK, String xHost, String contentType
, Map<String,Object> requestBody) throws Exception {
LocalDateTime localDateTime = LocalDateTime.now(); //At the current time
String xDate = localDateTimeToStringUTC(localDateTime); //UTC time format
URL url = new URL(urlAddress); // Create a URL object
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // Open the connection
connection.setRequestMethod("POST");
connection.setConnectTimeout(5000); // Set the connection timeout to 5 seconds
connection.setReadTimeout(5000); // Set the read timeout to 5 seconds
connection.setRequestProperty("content-type", contentType); // Set the request header
connection.setRequestProperty("x-host", xHost);
connection.setRequestProperty("x-date", xDate);
connection.setDoOutput(true); // Allows writing to request bodies
String authorization = AuthorizationUtil.authorizationCreate(AK, SK, xDate, xHost, contentType
, requestBody, "POST");
connection.setRequestProperty("authorization", authorization);
// Convert JSON data into byte arrays
byte[] outputInBytes = MapToJsonUtil.mapToJson(requestBody).getBytes(StandardCharsets.UTF_8);
try {
// Obtain the output stream and write the data
OutputStream os = connection.getOutputStream();
os.write(outputInBytes);
Scanner scanner = new Scanner(connection.getInputStream(), StandardCharsets.UTF_8.name());
StringBuilder response = new StringBuilder();
while (scanner.hasNext()) {
response.append(scanner.next());
}
System.out.println("POST Response Body: " + response);
return String.valueOf(response);
} catch (Exception e) {
e.printStackTrace();
} finally {
connection.disconnect();
}
return null;
}
/**
* Time zone switching
*
* @param localDateTime
* @return
*/
public static String localDateTimeToStringUTC(LocalDateTime localDateTime) {
ZoneId zoneId = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);
ZonedDateTime utcDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("UTC"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
return utcDateTime.format(formatter);
}
/**
* get request parameter simple parsing and concatenation
*
* @param map
* @return
*/
public static String getParamString(Map<String, Object> map) {
StringBuilder sb = new StringBuilder();
if (map != null) {
sb.append("?");
Iterator<String> keys = map.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
sb.append(key).append("=").append(map.get(key)).append("&");
}
// Remove the last '&' character (if present)
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
}
}
return sb.toString();
}
/**
* map to JsonUtil utility class
*/
public static class MapToJsonUtil {
public static String mapToJson(Map<String, Object> map) {
StringBuilder jsonString = new StringBuilder();
jsonString.append("{");
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
boolean firstEntry = true;
for (Map.Entry<String, Object> entry : entrySet) {
if (!firstEntry) {
jsonString.append(",");
}
firstEntry = false;
jsonString.append("\"");
jsonString.append(entry.getKey());
jsonString.append("\":");
Object value = entry.getValue();
if (value instanceof String) {
jsonString.append("\"");
jsonString.append(value);
jsonString.append("\"");
} else if (value instanceof Number || value instanceof Boolean) {
jsonString.append(value);
} else if (value instanceof Map) {
jsonString.append(mapToJson((Map<String, Object>) value));
} else if (value instanceof Iterable<?>) {
jsonString.append(iterableToJson((Iterable<?>) value));
} else if (value == null) {
jsonString.append("null");
} else {
// Handle other types if necessary
jsonString.append("\"");
jsonString.append(value.toString());
jsonString.append("\"");
}
}
jsonString.append("}");
return jsonString.toString();
}
private static String iterableToJson(Iterable<?> iterable) {
StringBuilder jsonArray = new StringBuilder();
jsonArray.append("[");
boolean firstElement = true;
for (Object element : iterable) {
if (!firstElement) {
jsonArray.append(",");
}
firstElement = false;
if (element instanceof String) {
jsonArray.append("\"");
jsonArray.append(element);
jsonArray.append("\"");
} else if (element instanceof Number || element instanceof Boolean) {
jsonArray.append(element);
} else if (element instanceof Map) {
jsonArray.append(mapToJson((Map<String, Object>) element));
} else if (element instanceof Iterable<?>) {
jsonArray.append(iterableToJson((Iterable<?>) element));
} else if (element == null) {
jsonArray.append("null");
} else {
// Handle other types if necessary
jsonArray.append("\"");
jsonArray.append(element.toString());
jsonArray.append("\"");
}
}
jsonArray.append("]");
return jsonArray.toString();
}
}
/**
* Signature tool class
*/
public static class AuthorizationUtil {
private static final String service = "armcloud-paas"; //Fixed service name
private static final String algorithm = "HMAC-SHA256"; //Fixed signature algorithm
/**
* authorization generation tool method
*
* @param Access Key ID for the AK account
* @param Secret Access Key for SK accounts
* @param xDate follows the ISO 8601 standard time format: YYYYMMDD'T'HHMMSS'Z' for example: 20201103T104027Z
* @param xHost fixed domain
* @param contentTypeJson requests content formatting
* @param requestBody Requests JSON data
* @param httpMethodStr Request method: GET or POST
* @return authorization
* @throws Exception
*/
public static String authorizationCreate(String AK, String SK, String xDate, String xHost
, String contentTypeJson, Map<String,Object> requestBody, String httpMethodStr) throws Exception {
byte[] body = new byte[0];
if (requestBody != null) {
if ("GET".equals(httpMethodStr)) {
StringBuilder sb = new StringBuilder();
Iterator<String> keys = requestBody.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
sb.append(key).append("=").append(requestBody.get(key)).append("&");
}
// Remove the last '&' character (if present)
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
}
body = sb.toString().getBytes(StandardCharsets.UTF_8);
} else {
body = MapToJsonUtil.mapToJson(requestBody).getBytes(StandardCharsets.UTF_8);
}
}
final String xContentSha256 = hashSHA256(body);
//Short-term format
final String shortDate = xDate.substring(0, 8);
String signedHeaders = "content-type;host;x-content-sha256;x-date";
String canonicalStringBuilder =
"host:" + xHost + "\n" + "x-date:" + xDate + "\n" + "content-type:" + contentTypeJson + "\n"
+ "signedHeaders:" + signedHeaders + "\n" + "x-content-sha256:" + xContentSha256;
String hashCanonicalString = hashSHA256(canonicalStringBuilder.getBytes());
String credentialScope = shortDate + "/" + service + "/request";
String signString = algorithm + "\n" + xDate + "\n" + credentialScope + "\n" + hashCanonicalString;
byte[] signKey = genSigningSecretKeyV4(SK, shortDate, service);
String signature = bytesToHex(hmacSHA256(signKey, signString));
//authorization
String authorization = "HMAC-SHA256 Credential=" + AK + "/" + xDate + "/" + service
+ "/request, SignedHeaders=" + signedHeaders + ", Signature=" + signature;
return authorization;
}
private static byte[] genSigningSecretKeyV4(String secretKey, String shortXDate
, String service) throws Exception {
byte[] kDate = hmacSHA256((secretKey).getBytes(), shortXDate);
byte[] kService = hmacSHA256(kDate, service);
return hmacSHA256(kService, "request");
}
private static String hashSHA256(byte[] content) throws Exception {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
return bytesToHex(md.digest(content));
} catch (Exception e) {
throw new Exception("Unable to compute hash while signing request: " + e.getMessage(), e);
}
}
private static String bytesToHex(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return "";
}
final StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
private static byte[] hmacSHA256(byte[] key, String content) throws Exception {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(content.getBytes());
} catch (Exception e) {
throw new Exception("Unable to calculate a request signature: " + e.getMessage(), e);
}
}
}
}
SDK-PYTHON reference template
[Download SDK-PYTHON] (https://oss-hk01.armcloud.net/arm-cloud-sdk/sdk_python.zip)
import hmac
import hashlib
import json
from typing import Dict, Optional
# Define the constant
SERVICE = "armcloud-paas"
ALGORITHM = "HMAC-SHA256"
X_HOST = "openapi-hk.armcloud.net"
CONTENT_TYPE_JSON = "application/json"
def authorization_create(ak: str, sk: str, request_body: Optional[Dict], x_date: str, http_method: str) -> str:
"""
Generates the Authorization request header
:param ak: Access Key
:param sk: Secret Key
:param request_body: request form (dictionary form)
:param x_date: Timestamp
:param http_method: HTTP Method (GET/POST)
:return: Authorization request header
"""
body = b""
if request_body is not None:
if http_method.upper() == "GET":
# When GET requests, concatenate the parameters as key1=value1&key2=value2
body = "&".join(f"{key}={value}" for key, value in request_body.items()).encode("utf-8")
else:
# When POSTing a request, the request body is converted into a JSON string
body = map_to_json(request_body).encode("utf-8")
x_content_sha256 = hash_sha256(body)
short_date = x_date[:8]
signed_headers = "content-type;host;x-content-sha256;x-date"
canonical_string = (
f"host:{X_HOST}\n"
f"x-date:{x_date}\n"
f"content-type:{CONTENT_TYPE_JSON}\n"
f"signedHeaders:{signed_headers}\n"
f"x-content-sha256:{x_content_sha256}"
)
hash_canonical_string = hash_sha256(canonical_string.encode("utf-8"))
credential_scope = f"{short_date}/{SERVICE}/request"
sign_string = f"{ALGORITHM}\n{x_date}\n{credential_scope}\n{hash_canonical_string}"
sign_key = gen_signing_secret_key_v4(sk, short_date, SERVICE)
signature = bytes_to_hex(hmac_sha256(sign_key, sign_string.encode("utf-8")))
authorization = (
f"{ALGORITHM} Credential={ak}/{x_date}/{SERVICE}/request, "
f"SignedHeaders={signed_headers}, Signature={signature}"
)
return authorization
def gen_signing_secret_key_v4(secret_key: str, short_x_date: str, service: str) -> bytes:
"""
Generate the signature key
:param secret_key: Key
:param short_x_date: Short Date (yyyyMMdd)
:param service: Service name
:return: Byte data of the signature key
"""
k_date = hmac_sha256(secret_key.encode("utf-8"), short_x_date.encode("utf-8"))
k_service = hmac_sha256(k_date, service.encode("utf-8"))
return hmac_sha256(k_service, b"request")
def hash_sha256(content: bytes) -> str:
"""
Calculate the SHA-256 hash value
:param content: Input content (byte data)
:return: The hexadecimal string of the hash value
"""
sha256 = hashlib.sha256()
sha256.update(content)
return sha256.hexdigest()
def bytes_to_hex(bytes_data: bytes) -> str:
"""
Convert byte data into hexadecimal strings
:param bytes_data: Byte data
:return: Hexadecimal string
"""
if not bytes_data:
return ""
return "".join(f"{byte:02x}" for byte in bytes_data)
def hmac_sha256(key: bytes, content: bytes) -> bytes:
"""
Calculate HMAC-SHA256
:param key: Key (byte data)
:param content: Input content (byte data)
:return: Byte data of HMAC results
"""
return hmac.new(key, content, hashlib.sha256).digest()
def map_to_json(map_obj):
json_string = "{"
first_entry = True
for key, value in map_obj.items():
if not first_entry:
json_string += ","
first_entry = False
json_string += f'"{key}":'
if isinstance(value, str):
json_string += f'"{value}"'
elif isinstance(value, (int, float, bool)):
json_string += str(value).lower() if isinstance(value, bool) else str(value)
elif isinstance(value, dict):
json_string += map_to_json(value)
elif isinstance(value, (list, tuple)):
json_string += iterable_to_json(value)
elif value is None:
json_string += "null"
else:
json_string += f'"{str(value)}"'
json_string += "}"
return json_string
def iterable_to_json(iterable):
json_string = "["
first_item = True
for item in iterable:
if not first_item:
json_string += ","
first_item = False
if isinstance(item, str):
json_string += f'"{item}"'
elif isinstance(item, (int, float, bool)):
json_string += str(item).lower() if isinstance(item, bool) else str(item)
elif isinstance(item, dict):
json_string += map_to_json(item)
elif isinstance(item, (list, tuple)):
json_string += iterable_to_json(item)
elif item is None:
json_string += "null"
else:
json_string += f'"{str(item)}"'
json_string += "]"
return json_string
# Example usage
if __name__ == "__main__":
ak = "ak"
sk = "sk"
request_body = {} # Request body
x_date = "20250126T230940Z" # Timestamp
http_method = "GET" # HTTP method
authorization = authorization_create(ak, sk, request_body, x_date, http_method)
print("Authorization Header:", authorization)