すべてのプロダクト
Search
ドキュメントセンター

Compute Nest:Compute Nest におけるデジタル署名の検証

最終更新日:Jun 21, 2026

このトピックでは、デジタル署名を検証する方法について説明します。

背景情報

Compute Nest 内の ECS インスタンスから CheckoutLicensePushMeteringData などの API 呼び出しを行う場合、Compute Nest からのレスポンスには Token フィールドにデジタル署名が含まれます。サービスプロバイダーは、デジタル署名を計算し、計算した値とレスポンスの Token 値を比較できます。このプロセスにより、レスポンスデータが改ざんされていないことを検証します。

操作手順

この例では、CheckoutLicense API 呼び出しのレスポンスを使用してデジタル署名を計算する方法を示します。

  1. 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"
        }
    }
  2. result オブジェクトから Token フィールドを除くすべてのフィールドを抽出します。残りのフィールドをキーでアルファベット順にソートし、アンパサンド (&) を使用して文字列に連結します。

    連結された文字列は次のとおりです:

    ExpireTime=2022-11-10T08:03:16Z&LicenseMetadata={"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"}&RequestId=CF54B4C9-E54C-1405-9A37-A0FE3D60****&ServiceInstanceId=si-85a343279cf341c2****
    説明

    LicenseMetadata を使用するサービスの場合、署名の不一致を防ぐために次の点に注意してください:

    • テンプレート内のパラメータ (仕様名やテンプレート名など) には、中国語の文字を含めないでください。

    • 署名文字列を構築する際は、LicenseMetadata フィールドで返された JSON 文字列をコンパクト形式に変換する必要があります (例: {"TemplateName":"Custom_Image_Ecs","SpecificationName":"dataDiskSize","CustomData":"30T"})。

  3. ソートされた文字列の末尾にサービスキーを追加します。

    Compute Nest コンソールの [サービス詳細] ページの [サービスキー] フィールドからサービスキーを取得します。

    サービスキーを追加すると、文字列は次のようになります:

    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
  4. 処理された文字列の MD5 ハッシュを計算します。出力は 32 文字の小文字の値です。

    ハッシュ化後、結果の値は次のとおりです: 21292abff855ab5c2a03809e0e4fb048

    計算した Token 値と Compute Nest から返された値を比較します。2 つの値が一致する場合、データは改ざんされていません。

サンプルコード

説明

コード内のサンプルレスポンスは、「Verify service instance validity」および「PushMeteringData - Push metering data」から引用されています。

Python

import json
import hashlib
def format_value(value):
    """パラメータ値をフォーマットし、さまざまなデータ型を処理します。"""
    if isinstance(value, bool):
        return "true" if value else "false"
    elif isinstance(value, dict):
        # 辞書を処理し、「key=value」形式の文字列を作成します。ペアはカンマとスペースで区切られます。
        items = []
        for k, v in value.items():
            # ブール値は小文字の文字列に、その他の型は文字列に変換します。
            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):
        # リストを処理し、JSON 形式に直接変換します。
        return json.dumps(value, separators=(',', ':'))
    elif isinstance(value, str):
        try:
            # 文字列を JSON オブジェクトとして解析しようとします。
            parsed = json.loads(value)
            if isinstance(parsed, dict):
                # オブジェクトが辞書の場合、標準 JSON 形式に変換します。
                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", {})
    # 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"Concatenated string to sign: {query_string}")
    # サービスキーを追加します。
    final_str = f"{query_string}&Key={service_key}"
    print(f"Final string with service key: {final_str}")
    # 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"Calculated Token: {calculated_token}")
    print(f"Returned Token: {json.loads(response_json)['result']['Token']}")
    # トークンが一致するかどうかを検証します。
    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";
    // --- テストケース ---
    public static void main(String[] args) {
        String responseJson = "{\n" +
                "    \"code\": 200,\n" +
                "    \"requestId\": \"4ea52d12-8e28-440b-b454-938d0518****\",\n" +
                "    \"instanceId\": \"i-0jl1ej1czubkimg6****\",\n" +
                "    \"result\": {\n" +
                "        \"RequestId\": \"CF54B4C9-E54C-1405-9A37-A0FE3D60****\",\n" +
                "        \"ServiceInstanceId\": \"si-85a343279cf341c2****\",\n" +
                "        \"LicenseMetadata\": \"{\\\"TemplateName\\\":\\\"Custom_Image_Ecs\\\",\\\"SpecificationName\\\":\\\"dataDiskSize\\\",\\\"CustomData\\\":\\\"30T\\\"}\",\n" +
                "        \"Token\": \"21292abff855ab5c2a03809e0e4fb048\",\n" +
                "        \"ExpireTime\": \"2022-11-10T08:03:16Z\"\n" +
                "    }\n" +
                "}";
        String serviceProviderKey = "37131c4a485141xxxxxx";
        boolean isValid = verifyToken(responseJson, serviceProviderKey);
        System.out.println("Token verification result: " + isValid);
    }
    /**
     * JSON レスポンスからトークンを計算して検証します。
     */
    public static boolean verifyToken(String responseJsonStr, String serviceProviderKey) {
        try {
            // 1. JSON レスポンスを解析します。
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode responseNode = objectMapper.readTree(responseJsonStr);
            JsonNode resultNode = responseNode.get("result");
            // 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. パラメータをソートします。
            List<Map.Entry<String, String>> sortedParams = new ArrayList<>(params.entrySet());
            sortedParams.sort(Comparator.comparing(entry -> entry.getKey().toLowerCase()));
            // 4. 連結された文字列を生成します。
            String urlParams = buildOrderUrlParams(sortedParams);
            String finalStr = urlParams + "&Key=" + serviceProviderKey;
            System.out.println("Final string to sign: " + finalStr);
            // 5. MD5 ハッシュを計算します。
            String calculatedToken = generateMD5(finalStr);
            // 6. 返されたトークンを取得します。
            String returnedToken = resultNode.get(TOKEN_KEY).asText();
            System.out.println("Calculated Token: " + calculatedToken);
            System.out.println("Returned Token: " + returnedToken);
            // 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」形式の文字列を作成します。
                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("Failed to calculate MD5 hash", e);
        }
    }
}
説明

プロジェクトに com.fasterxml.jackson.core ライブラリが含まれていることを確認してください。例えば、Maven 依存関係に次のように追加します:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version> <!-- 最新の安定版を使用することを推奨します。 -->
</dependency>