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

Compute Nest:Compute Nest デジタル署名の検証

最終更新日:Jun 08, 2025

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

背景情報

Compute Nest Elastic Compute Service (ECS) で CheckoutLicensePushMeteringData 操作などの API 呼び出しを開始すると、Compute Nest はデジタル署名情報 (Token) を返します。サービスプロバイダーは、以下の方法を使用してデジタル署名の値を計算し、計算されたデジタル署名を Compute Nest から返されたデジタル署名と比較することで、データが改ざんされているかどうかを判断できます。

メッセージングプロセス

この例では、CheckoutLicense の戻り値を使用してデジタル署名の値を計算します。

  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. パラメーターフィールドを取得し、デジタル署名 (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"} に変換されます。

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

    サービスキーの文字列形式は 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=37131c4a485141xxxxxx
  4. MD5 暗号化アルゴリズムを使用して、処理された文字列を暗号化し、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>