このトピックでは、Compute Nest でデジタル署名情報を検証する方法について説明します。
背景情報
Compute Nest Elastic Compute Service (ECS) で CheckoutLicense や PushMeteringData 操作などの API 呼び出しを開始すると、Compute Nest はデジタル署名情報 (Token) を返します。サービスプロバイダーは、以下の方法を使用してデジタル署名の値を計算し、計算されたデジタル署名を Compute Nest から返されたデジタル署名と比較することで、データが改ざんされているかどうかを判断できます。
メッセージングプロセス
この例では、CheckoutLicense の戻り値を使用してデジタル署名の値を計算します。
CheckoutLicense操作を呼び出して、戻り値を取得します。curl -H "Content-Type: application/json" -XPOST https://cn-wulanchabu.axt.aliyun.com/computeNest/license/check_out_license -d '{}'サンプルレスポンス
{ "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" } }パラメーターフィールドを取得し、デジタル署名 (Token) 情報を削除し、パラメーターをアルファベット順にソートし、アンパサンド記号 (&) で連結します。
連結された文字列のサンプル:
ExpireTime=2022-11-02T02:39:43Z&LicenseMetadata={"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}&RequestId=CF54B4C9-E54C-1405-9A37-A0FE3D60xxxx&ServiceInstanceId=si-85a343279cf341c2xxxx説明ライセンスメタデータ (
LicenseMetadata) を持つサービスの場合、署名の不一致を防ぐために、以下の項目に注意してください。テンプレート内のパラメーターに漢字 (パラメーターセット名やテンプレート名など) を含めることはできません。
返された JSON コンテンツは
{"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}に変換されます。
ソートされた文字列の末尾にサービスキーを追加します。
サービスキーの文字列形式は Key={ServiceProviderKey} です。サービスキーはサービス詳細ページで取得できます。

サービスキーを追加した文字列のサンプル:
ExpireTime=2022-11-02T02:39:43Z&LicenseMetadata={"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}&RequestId=CF54B4C9-E54C-1405-9A37-A0FE3D60xxxx&ServiceInstanceId=si-85a343279cf341c2xxxx&Key=37131c4a485141xxxxxxMD5 暗号化アルゴリズムを使用して、処理された文字列を暗号化し、32 文字の小文字の値を生成します。
文字列が暗号化されると、戻り値は
21292abff855ab5c2a03809e0e4fb048になります。サービスプロバイダーは、上記の方法で計算された Token 値と Compute Nest から返された Token 値を比較できます。2 つの値が同じであれば、データは改ざんされていません。
サンプルコード
コード内のサンプルレスポンスを取得するには、サービスインスタンスの有効期間を確認する と PushMeteringData をご参照ください。
Python
import json
import hashlib
def format_value(value):
"""パラメーター値をフォーマットして、さまざまなパラメータータイプを処理します""" // パラメーター値をフォーマットして、さまざまなパラメータータイプを処理します
if instance(value, bool):
return "true" if value else "false"
elif instance(value, dict):
# 辞書型を処理し、key=value 形式を生成し、カンマ (,) とスペースでキーと値のペアを区切ります。 // 辞書型を処理し、key=value 形式を生成し、カンマ (,) とスペースでキーと値のペアを区切ります。
items = []
for k, v in value.items():
# ブール値を小文字の文字列に変換し、その他の値を文字列に変換します。 // ブール値を小文字の文字列に変換し、その他の値を文字列に変換します。
v_str = str(v).lower() if instance(v, bool) else str(v)
items.append(f"{k}={v_str}")
return "{" + ", ".join(items) + "}"
elif instance(value, list):
# リスト型を処理し、コンテンツを JSON 形式に変換します。 // リスト型を処理し、コンテンツを JSON 形式に変換します。
return json.dumps(value, separators=(',', ':'))
elif instance(value, str):
try:
# 文字列を JSON オブジェクト (Components 値など) として解析してみます // 文字列を JSON オブジェクト (Components 値など) として解析してみます
parsed = json.loads(value).
if instance(parsed, dict):
# 辞書形式の場合は、標準の JSON 形式に変換します。 // 辞書形式の場合は、標準の JSON 形式に変換します。
return json.dumps(parsed, separators=(',', ':'))
elif instance(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", {})
# Token 以外のパラメーターをすべて抽出し、フォーマットします。 // Token 以外のパラメーターをすべて抽出し、フォーマットします。
params = {}
for key, value in result.items():
if key.lower() != "token":
formatted_value = format_value(value)
params[key] = formatted_value
# パラメーターをアルファベット順にソートします。 // パラメーターをアルファベット順にソートします。
sorted_params = sorted(params.items(), key=lambda x: x[0].lower())
# 連結された文字列を生成します。 // 連結された文字列を生成します。
query_string = "&".join(f"{k}={v}" for k, v in sorted_params)
print(f"生成された連結文字列: {query_string}") // 生成された連結文字列: {query_string}
# サービスキーを追加します。 // サービスキーを追加します。
final_str = f"{query_string}&Key={service_key}"
print(f"サービスキーを追加した後の文字列: {final_str}") // サービスキーを追加した後の文字列: {final_str}
# MD5 を計算します。 // MD5 を計算します。
md5_hash = hashlib.md5(final_str.encode()).hexdigest().lower()
return md5_hash
# 使用例 // 使用例
if __name__ == "__main__":
# サンプルレスポンス (コンテンツを実際のレスポンスコンテンツに置き換えます) // サンプルレスポンス (コンテンツを実際のレスポンスコンテンツに置き換えます)
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" # 実際のサービスキーに置き換えます // 実際のサービスキーに置き換えます
calculated_token = calculate_token(response_json, service_key)
print(f"計算された Token: {calculated_token}") // 計算された Token: {calculated_token}
print(f"返された Token: {json.loads(response_json)['result']['Token']}") // 返された Token: {json.loads(response_json)['result']['Token']}
# 整合性を検証します。 // 整合性を検証します。
print(f"検証結果: {calculated_token == json.loads(response_json)['result']['Token']}") // 検証結果: {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";
// --- テストケース --- // --- テストケース ---
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 検証結果: " + isValid); // Token 検証結果: + isValid
}
/**
* JSON レスポンスに基づいてトークンを計算および検証します。 // JSON レスポンスに基づいてトークンを計算および検証します。
*/
public static boolean verifyToken(String responseJsonStr, String serviceProviderKey) {
try {
// 1. JSON レスポンスを解析します。 // 1. JSON レスポンスを解析します。
ObjectMapper objectMapper = new ObjectMapper();
JsonNode responseNode = objectMapper.readTree(responseJsonStr);
JsonNode resultNode = responseNode.get("result");
// 2. パラメーターを抽出してフォーマットします。 // 2. パラメーターを抽出してフォーマットします。
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. パラメーターをソートします。 // 3. パラメーターをソートします。
List<Map.Entry<String, String>> sortedParams = new ArrayList<>(params.entrySet());
sortedParams.sort(Comparator.comparing(entry -> entry.getKey().toLowerCase()));
// 4. 連結文字列を生成します。 // 4. 連結文字列を生成します。
String urlParams = buildOrderUrlParams(sortedParams);
String finalStr = urlParams + "&Key=" + serviceProviderKey;
System.out.println("連結文字列: " + finalStr); // 連結文字列: + finalStr
// 5. MD5 値を計算します。 // 5. MD5 値を計算します。
String calculatedToken = generateMD5(finalStr);
// 6. 返されたトークンを取得します。 // 6. 返されたトークンを取得します。
String returnedToken = resultNode.get(TOKEN_KEY).asText();
System.out.println("計算された Token: " + calculatedToken); // 計算された Token: + calculatedToken
System.out.println("返された Token: " + returnedToken); // 返された Token: + returnedToken
// 7. 整合性を検証します。 // 7. 整合性を検証します。
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()) {
// オブジェクトタイプを処理し、key=value 形式で値を生成します。 // オブジェクトタイプを処理し、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()) {
// 配列タイプを処理します (必要に応じて) // 配列タイプを処理します (必要に応じて)
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); // 最後のアンパサンド記号 (&) を削除します。 // 最後のアンパサンド記号 (&) を削除します。
}
return sb.toString();
}
private static String generateMD5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = String.format("%02x", b);
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException("MD5 計算に失敗しました", e); // MD5 計算に失敗しました
}
}
}プロジェクトに com.fasterxml.jackson.core ライブラリ (Maven 依存関係など) が含まれていることを確認してください。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version> <!-- 最新の安定バージョンを使用することをお勧めします --> <!-- 最新の安定バージョンを使用することをお勧めします -->
</dependency>