API認証署名の基本と実践ガイド

目次
  • 加解密の基礎
  • 暗号化方式の分類
  • 対称暗号技術


- 非対称暗号技術(RSAアルゴリズム)(デジタル証明書)
- ```
  * シナリオ1:公開鍵で暗号化、秘密鍵で復号

  • シナリオ2:秘密鍵で署名:デジタル署名、公開鍵で検証
  • ハッシュアルゴリズム(MD5)
  • * APIテストツール
    
    


- Postman
- Jmeter
- API認証署名の原理
- ```
* 認証署名とは何か

  • なぜ認証署名が必要か
  • 認証署名の実装方法
  • パラメータをキーでASCII順にソート


- パラメータ名と値を連結
- APIキーとシークレットを文字列の先頭に連結
- タイムスタンプを文字列の末尾に追加して有効期限検証を実装
- リプレイ攻撃を防ぐためのランダム値nonceの追加
- 完成した文字列をMD5でハッシュ化
- 認証署名の実装例
- ```
* 例1

加解密の基礎

暗号化:ネットワーク上で送信される生データ(平文)が暗号化アルゴリズムによって暗号化され(暗号文)送信され、盗難を防ぐ 復号:暗号文を元のデータに復元する

暗号化方式の分類

対称暗号:暗号化と復号に同じ鍵を使用 非対称暗号:非対称暗号には2つの鍵(公開鍵と秘密鍵)が必要で、それぞれが互いに暗号化と復号が可能です。公開鍵は公開されますが、秘密鍵は秘匿されます。

対称暗号技術

  • DES暗号化アルゴリズム
  • AES暗号化アルゴリズム
  • Base64アルゴリズム

https://www.bejson.com/enc/aesdes/ で体験できます DESとAESは毎回暗号化すると暗号文が異なりますが、Base64は暗号化後の暗号文が固定されます

非対称暗号技術(RSAアルゴリズム)(デジタル証明書)

https://www.bejson.com/enc/rsa/ で公開鍵と秘密鍵をオンライン生成し、公開鍵での暗号化と秘密鍵での復号をテストできます

シナリオ1:公開鍵で暗号化、秘密鍵で復号

2人のユーザーAとBがおり、Bが鍵ペアを持っています。Aはデータを暗号化してBに送信したい

シナリオ2:秘密鍵で署名:デジタル署名、公開鍵で検証:署名検証

デジタル証明書の由来:公開鍵は公開されているため安全ではないため、第三者のCA(認証局)が公開鍵を暗号化し、暗号化されたものがデジタル証明書です。 デジタル証明書には、Bユーザーの基本情報とBの公開鍵情報が含まれます。X509標準

CA:鍵ペアを持ち、秘密鍵で暗号化

FiddlerはHTTPSプロトコルのデータパケットを直接キャプチャできず、デジタル証明書のインストールが必要です https = http + ssl安全転送プロトコル

ssl安全転送プロトコル:安全ソケットレイヤー、NetScapeが開発

ハッシュアルゴリズム(MD5)(復号を全く考慮せず、ハッシュアルゴリズムとも呼ばれる)

Postman
Jmeter

${__digest(MD5,admin,)}

API認証署名の原理

認証署名とは何か

認証署名:ユーザー名、パスワード、タイムスタンプ、およびソートされたすべてのパラメータを組み合わせ、暗号化して生成される文字列です。この文字列がAPIにアクセスするための一意の認証コードです ユーザー名:APIキー パスワード:APIシークレット

なぜ認証署名が必要か

  • 偽装攻撃の防止
  • 改ざん攻撃の防止
  • リプレイ攻撃の防止
  • 情報漏洩の防止

アクセス者の正当性を保証し、パラメータが変更されていないことを確認し、リクエストの一意性を確保します

認証署名の実装方法

パラメータをキーでASCII順にソート

例えばパラメータが?c=1&b=2&a=3の場合 ソート後はa、b、cになります

パラメータ名と値を連結した文字列を作成

a=1&b=2&c=3

APIキーとシークレットを文字列の先頭に連結

api_key=admin&api_secret=123&a=1&b=2&c=3

タイムスタンプを文字列の末尾に追加して有効期限検証を実装

例えば1分以内に有効

api_key=admin&api_secret=123&a=1&b=2&c=3&timestamp=1666757432136

リプレイ攻撃を防ぐためのランダム値nonceを追加

nonceはランダム数で、UUIDが望ましい

api_key=admin&api_secret=123&a=1&b=2&c=3&timestamp=1666757432136&nonce=123123

完成した文字列をMD5でハッシュ化

String str = "api_key=admin&api_secret=123&a=1&b=2&c=3&timestamp=1666757432136&nonce=123123";
String signature = md5(str);

認証署名の実装例

例1

署名アルゴリズム 署名アルゴリズムの説明: 1.リクエストパラメータをパラメータ名で昇順ソートします; 2.リクエストパラメータ名とパラメータ値を相互に連結して文字列を作成します; 3.アプリケーションシークレットをリクエストパラメータ文字列の先頭と末尾に追加します; 4.この文字列をMD5(すべて大文字)でハッシュ化し、その文字列がリクエストパラメータに対応する署名です; 5.この署名値はsignパラメータと共に、他のリクエストパラメータと一緒にサービス提供プラットフォームに送信されます。

疑似コード:

Map<String,Object> requestParams = new ...; // パラメータ

Set<String> keys = requestParams.keySet();
List<String> paramNames = new ArrayList<String>(keys);
// 1.
Collections.sort(paramNames);

StringBuilder paramBuilder = new StringBuilder();
// 2.
for (String paramName : paramNames) {
    paramBuilder.append(paramName).append(requestParams.get(paramName));
}
// 3.
String source = secret + paramBuilder.toString() + secret;
// 4.
String signature = md5(source);
// 5.
requestParams.put("signature",signature);

コード例

import java.io.IOException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.junit.Test;

import com.alibaba.fastjson.JSON;

import junit.framework.TestCase;

public class ApiSecurityTest extends TestCase {

    @Test
    public void testApiRequest() throws IOException {
      String apiKey = "xxx";
        String apiSecret = "xxx";
        // ビジネスパラメータ
        Map businessParams = new HashMap();
        businessParams.put("dicCode", "terminalType");

        String jsonParams = JSON.toJSONString(businessParams);
        jsonParams = URLEncoder.encode(jsonParams, "utf-8");

        // システムパラメータ
        Map systemParams = new HashMap();
        systemParams.put("methodName", "dictionaryItem.list");
        systemParams.put("apiVersion", "1.0");
        systemParams.put("apiKey", apiKey);
        systemParams.put("requestData", jsonParams);
        systemParams.put("timestamp", getCurrentTime());
        systemParams.put("responseFormat", "json");

        String signature = createSignature(systemParams, apiSecret);
        systemParams.put("signature", signature);

        /*
        // 最終リクエストデータ
       {
           "methodName":"dictionaryItem.list",
           "apiVersion":"1.0",
           "apiKey":"test",
           "requestData":"%7B%22dicCode%22%3A%22terminalType%22%7D",
           "timestamp":"2021-12-15 10:25:02",
           "responseFormat":"json",
           "signature":"4B291FFFFDD6F0E3FB9708AC0F7AC334"
       }

        */
        System.out.println("=====リクエストデータ=====");
        String requestJson = JSON.toJSONString(systemParams);
        System.out.println(requestJson);
        // contentType:application/json
        // requestJsonをリクエストボディに設定
        // リクエスト送信
        String response = HttpRequest.post("https://xxx.net/api").body(requestJson).execute().body();
        System.out.println(response);
        /*
        レスポンス結果:
        {
            "code":"0",
            "data":[
                {"dicCode":"terminalType","isplay":1,"itemId":120,"itemName":"音柱/音箱","itemValue":"1","sort":1},
                {"dicCode":"terminalType","isplay":1,"itemId":121,"itemName":"AIO报警箱","itemValue":"2","sort":2},
                {"dicCode":"terminalType","isplay":1,"itemId":122,"itemName":"融媒体客服主机","itemValue":"3","sort":3},
                {"dicCode":"terminalType","isplay":1,"itemId":123,"itemName":"网络调音台","itemValue":"4","sort":4},
                {"dicCode":"terminalType","isplay":1,"itemId":124,"itemName":"云广播适配器","itemValue":"5","sort":5},
                {"dicCode":"terminalType","isplay":1,"itemId":125,"itemName":"音频编码器","itemValue":"6","sort":6},
                {"dicCode":"terminalType","isplay":1,"itemId":126,"itemName":"村级播控主机","itemValue":"7","sort":7},
                {"dicCode":"terminalType","isplay":1,"itemId":127,"itemName":"云话筒","itemValue":"8"},
                {"dicCode":"terminalType","isplay":1,"itemId":128,"itemName":"安卓手机客户端","itemValue":"9"},
                {"dicCode":"terminalType","isplay":1,"itemId":129,"itemName":"收扩机","itemValue":"10","sort":8}
            ]
        }
        */
    }

    /**
     * 署名の作成
     *
     * @param params
     *            パラメータマップ
     * @param secret
     *            シークレットキー
     * @return
     * @throws IOException
     */
    public static String createSignature(Map<String, ?> params, String secret) throws IOException {
        Set<String> keySet = params.keySet();
        List<String> sortedKeys = new ArrayList<String>(keySet);

        Collections.sort(sortedKeys);

        StringBuilder paramBuilder = new StringBuilder();

        for (String key : sortedKeys) {
            paramBuilder.append(key).append(params.get(key));
        }

        String source = secret + paramBuilder.toString() + secret;

        return generateMD5(source);
    }

    /**
     * MD5を生成(すべて大文字)
     *
     * @param input
     * @return
     */
    public static String generateMD5(String input) {
        try {
            // 1 メッセージダイジェストアルゴリズムオブジェクトを作成し、MD5アルゴリズムオブジェクトとして初期化
            MessageDigest md = MessageDigest.getInstance("MD5");

            // 2 メッセージをバイト配列に変換
            byte[] messageBytes = input.getBytes();

            // 3 計算してバイト配列を取得(128ビット)
            byte[] digestBytes = md.digest(messageBytes);

            // 4 配列の各バイト(1バイトは8ビット)を16進数に変換してMD5文字列に連結
            return bytesToHex(digestBytes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * バイト配列を16進数文字列に変換
     *
     * @param bytes
     * @return
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                hexString.append("0");
            }
            hexString.append(hex.toUpperCase());
        }
        return hexString.toString();
    }

    /**
     * 16進数文字列をバイト配列に変換
     *
     * @param hexString
     * @return
     */
    private static byte[] hexToBytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] data = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            data[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return data;
    }

    /**
     * バイト配列からオーディオファイルを生成
     *
     * @param audioData バイト配列
     * @param filePath ファイルパス
     * @return
     */
    public static String createAudioFile(byte[] audioData, String filePath) throws IOException {
        // 方法1:直接ファイルを生成
        String fileUrl = filePath + ".mp3";
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
        FileOutputStream outputStream = new FileOutputStream(file);
        outputStream.write(audioData);
        // 方法2:バイト配列をクラウドストレージにアップロードしてファイルを生成
        // String fileUrl = "http://" + OSSFactory.build().uploadSuffix(audioData, ".mp3");
        return fileUrl;
    }


    public String getCurrentTime() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    }
}

セキュリティエンジニア(ホワイトハット)向け企業レベル学習ロードマップ

第一段階:セキュリティ基礎(入門)

第二段階:Webペネトレーションテスト(ジュニアセキュリティエンジニア)

第三段階:中級部分(中級ネットワークセキュリティエンジニア)

ネットワークセキュリティ入門に興味がある場合、必要であればここをクリックしてください:ネットワークセキュリティ特別特典:入門&中級向け282G学習リソースパックを無料で共有!

学習リソース共有

タグ: API認証 セキュリティ デジタル署名 MD5ハッシュ

6月26日 20:41 投稿