Topik ini menjelaskan cara memverifikasi signature digital.
Latar Belakang
Saat Anda melakukan panggilan API, seperti CheckoutLicense atau PushMeteringData, dari instance ECS di Compute Nest, respons dari Compute Nest mencakup signature digital dalam bidang Token. Sebagai penyedia layanan, Anda dapat menghitung signature digital tersebut dan membandingkan hasil perhitungan Anda dengan nilai Token dalam respons. Proses ini memverifikasi bahwa data respons tidak dimodifikasi.
Prosedur
Contoh berikut menunjukkan cara menghitung signature digital menggunakan respons dari panggilan API CheckoutLicense.
-
Panggil
CheckoutLicenseuntuk mendapatkan respons.curl -H "Content-Type: application/json" -XPOST https://cn-wulanchabu.axt.aliyun.com/computeNest/license/check_out_license -d '{}'Response
{ "code":200, "requestId":"4ea52d12-8e28-440b-b454-938d0518****", "instanceId":"i-0jl1ej1czubkimg6****", "result":{ "RequestId":"CF54B4C9-E54C-1405-9A37-A0FE3D60****", "ServiceInstanceId":"si-85a343279cf341c2****", "LicenseMetadata":"{\"TemplateName\":\"Custom_Image_Ecs\",\"SpecificationName\":\"dataDiskSize\",\"CustomData\":\"30T\"}", "Token":"21292abff855ab5c2a03809e0e4fb048", "ExpireTime":"2022-11-10T08:03:16Z" } } -
Ekstrak semua bidang dari objek
resultkecuali bidangToken. Urutkan bidang-bidang yang tersisa secara alfabetis berdasarkan kunci, lalu gabungkan menjadi satu string menggunakan tanda ampersand (&).String yang digabungkan adalah sebagai berikut:
ExpireTime=2022-11-10T08:03:16Z&LicenseMetadata={"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}&RequestId=CF54B4C9-E54C-1405-9A37-A0FE3D60****&ServiceInstanceId=si-85a343279cf341c2****CatatanUntuk layanan yang menggunakan
LicenseMetadata, perhatikan hal-hal berikut guna mencegah ketidaksesuaian signature:-
Parameter dalam templat, seperti nama spesifikasi dan nama templat, tidak boleh mengandung karakter Tionghoa.
-
Saat menyusun string signature, Anda harus mengonversi string JSON yang dikembalikan dalam bidang
LicenseMetadatake format ringkas, misalnya,{"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}.
-
-
Tambahkan kunci layanan Anda ke akhir string yang telah diurutkan.
Dapatkan kunci layanan Anda dari bidang Service Key pada halaman Service Details di Konsol Compute Nest.
Setelah menambahkan kunci layanan, string tersebut menjadi sebagai berikut:
ExpireTime=2022-11-10T08:03:16Z&LicenseMetadata={"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}&RequestId=CF54B4C9-E54C-1405-9A37-A0FE3D60****&ServiceInstanceId=si-85a343279cf341c2****&Key=37131c4a485141xxxxxx -
Hitung hash MD5 dari string yang telah diproses. Hasilnya berupa nilai 32 karakter dalam huruf kecil.
Setelah penghashan, nilai yang dihasilkan adalah:
21292abff855ab5c2a03809e0e4fb048.Bandingkan nilai
Tokenhasil perhitungan Anda dengan nilai yang dikembalikan oleh Compute Nest. Jika kedua nilai identik, data tidak dimodifikasi.
Kode contoh
Respons contoh dalam kode berasal dari Verify service instance validity dan PushMeteringData - Push metering data.
Python
import json
import hashlib
def format_value(value):
"""Memformat nilai parameter dan menangani berbagai tipe data."""
if isinstance(value, bool):
return "true" if value else "false"
elif isinstance(value, dict):
# Menangani dictionary dengan membuat string "key=value" dengan pasangan dipisahkan koma dan spasi.
items = []
for k, v in value.items():
# Mengonversi nilai boolean ke string huruf kecil dan tipe lain ke string.
v_str = str(v).lower() if isinstance(v, bool) else str(v)
items.append(f"{k}={v_str}")
return "{" + ", ".join(items) + "}"
elif isinstance(value, list):
# Menangani list dengan mengonversinya langsung ke format JSON.
return json.dumps(value, separators=(',', ':'))
elif isinstance(value, str):
try:
# Mencoba mengurai string sebagai objek JSON.
parsed = json.loads(value)
if isinstance(parsed, dict):
# Jika objek berupa dictionary, konversi ke format JSON standar.
return json.dumps(parsed, separators=(',', ':'))
elif isinstance(parsed, list):
return json.dumps(parsed, separators=(',', ':'))
else:
return value
except json.JSONDecodeError:
return value
else:
return str(value)
def calculate_token(response_json_str, service_key):
response = json.loads(response_json_str)
result = response.get("result", {})
# Ekstrak dan format semua parameter kecuali Token.
params = {}
for key, value in result.items():
if key.lower() != "token":
formatted_value = format_value(value)
params[key] = formatted_value
# Urutkan parameter secara alfabetis berdasarkan kunci.
sorted_params = sorted(params.items(), key=lambda x: x[0].lower())
# Hasilkan string yang digabungkan.
query_string = "&".join(f"{k}={v}" for k, v in sorted_params)
print(f"Concatenated string to sign: {query_string}")
# Tambahkan kunci layanan.
final_str = f"{query_string}&Key={service_key}"
print(f"Final string with service key: {final_str}")
# Hitung hash MD5.
md5_hash = hashlib.md5(final_str.encode()).hexdigest().lower()
return md5_hash
# Contoh
if __name__ == "__main__":
# Respons contoh. Ganti ini dengan respons sebenarnya.
response_json = r'''{
"code": 200,
"requestId": "4ea52d12-8e28-440b-b454-938d0518****",
"instanceId": "i-0jl1ej1czubkimg6****",
"result": {
"RequestId": "CF54B4C9-E54C-1405-9A37-A0FE3D60****",
"ServiceInstanceId": "si-85a343279cf341c2****",
"LicenseMetadata": "{\"TemplateName\":\"Custom_Image_Ecs\",\"SpecificationName\":\"dataDiskSize\",\"CustomData\":\"30T\"}",
"Token": "21292abff855ab5c2a03809e0e4fb048",
"ExpireTime": "2022-11-10T08:03:16Z"
}
}'''
service_key = "37131c4a485141xxxxxx" # Ganti ini dengan kunci layanan Anda yang sebenarnya.
calculated_token = calculate_token(response_json, service_key)
print(f"Calculated Token: {calculated_token}")
print(f"Returned Token: {json.loads(response_json)['result']['Token']}")
# Verifikasi apakah token cocok.
print(f"Verification result: {calculated_token == json.loads(response_json)['result']['Token']}")
Java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.MessageDigest;
import java.util.*;
public class TokenVerifier {
private static final String REQUEST_ID = "RequestId";
private static final String TOKEN_KEY = "Token";
// --- Test case ---
public static void main(String[] args) {
String responseJson = "{\n" +
" \"code\": 200,\n" +
" \"result\": {\n" +
" \"ExpireTime\": \"2022-11-02T02:39:43Z\",\n" +
" \"LicenseMetadata\": \"{\\\"TemplateName\\\":\\\"Custom_Image_Ecs\\\",\\\"SpecificationName\\\":\\\"dataDiskSize\\\",\\\"CustomData\\\":\\\"30T\\\"}\",\n" +
" \"RequestId\": \"CF54B4C9-E54C-1405-9A37-A0FE3D60****\",\n" +
" \"ServiceInstanceId\": \"si-85a343279cf341c2****\",\n" +
" \"Token\": \"21292abff855ab5c2a03809e0e4fb048\"\n" +
" }\n" +
"}";
String serviceProviderKey = "37131c4a485141xxxxxx";
boolean isValid = verifyToken(responseJson, serviceProviderKey);
System.out.println("Token verification result: " + isValid);
}
/**
* Menghitung dan memverifikasi token dari respons JSON.
*/
public static boolean verifyToken(String responseJsonStr, String serviceProviderKey) {
try {
// 1. Urai respons JSON.
ObjectMapper objectMapper = new ObjectMapper();
JsonNode responseNode = objectMapper.readTree(responseJsonStr);
JsonNode resultNode = responseNode.get("result");
// 2. Ekstrak dan format parameter.
Map<String, String> params = new HashMap<>();
Iterator<String> fieldNames = resultNode.fieldNames();
while (fieldNames.hasNext()) {
String key = fieldNames.next();
if (!key.equalsIgnoreCase(TOKEN_KEY)) {
Object value = resultNode.get(key);
String formattedValue = formatValue(value);
params.put(key, formattedValue);
}
}
// 3. Urutkan parameter.
List<Map.Entry<String, String>> sortedParams = new ArrayList<>(params.entrySet());
sortedParams.sort(Comparator.comparing(entry -> entry.getKey().toLowerCase()));
// 4. Hasilkan string yang digabungkan.
String urlParams = buildOrderUrlParams(sortedParams);
String finalStr = urlParams + "&Key=" + serviceProviderKey;
System.out.println("Final string to sign: " + finalStr);
// 5. Hitung hash MD5.
String calculatedToken = generateMD5(finalStr);
// 6. Dapatkan token yang dikembalikan.
String returnedToken = resultNode.get(TOKEN_KEY).asText();
System.out.println("Calculated Token: " + calculatedToken);
System.out.println("Returned Token: " + returnedToken);
// 7. Verifikasi apakah token cocok.
return calculatedToken.equals(returnedToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private static String formatValue(Object value) {
if (value instanceof Boolean) {
return (Boolean) value ? "true" : "false";
} else if (value instanceof JsonNode) {
JsonNode node = (JsonNode) value;
if (node.isObject()) {
// Menangani tipe objek dengan membuat string "key=value".
List<String> items = new ArrayList<>();
Iterator<String> fieldNames = node.fieldNames();
while (fieldNames.hasNext()) {
String key = fieldNames.next();
Object childValue = node.get(key);
String formattedChildValue = formatValue(childValue);
items.add(key + "=" + formattedChildValue);
}
return "{" + String.join(", ", items) + "}";
} else if (node.isArray()) {
// Menangani tipe array sesuai kebutuhan.
return node.toString().replace(" ", "");
} else {
return node.asText();
}
} else {
return value.toString();
}
}
private static String buildOrderUrlParams(List<Map.Entry<String, String>> entries) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : entries) {
sb.append(entry.getKey())
.append("=")
.append(entry.getValue())
.append("&");
}
if (sb.length() > 0) {
sb.setLength(sb.length() - 1); // Hapus ampersand (&) di akhir.
}
return sb.toString();
}
private static String generateMD5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = String.format("%02x", b);
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException("Failed to calculate MD5 hash", e);
}
}
}
Pastikan proyek Anda menyertakan library com.fasterxml.jackson.core, misalnya sebagai dependensi Maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version> <!-- Kami menyarankan agar Anda menggunakan versi stabil terbaru. -->
</dependency>