全部產品
Search
文件中心

Captcha:V3架構加密模式接入引導

更新時間:Dec 18, 2025

加密模式是一項可選的安全增強功能。啟用後,您的服務端需使用密钥(ekey)動態產生加密字串,並傳遞給前端用於驗證碼初始化。該模式可有效防止因 sceneId 泄露而導致的服務盜用或惡意調用,從而提升介面安全性。

重要

本文僅適用於V3架構接入。V2架構下,驗證介面由使用者的服務端發起,無需開啟加密模式。

工作原理

  1. 服務端產生加密字串:商務服務端使用從驗證碼控制台擷取的密钥(ekey),將情境ID(sceneId)、時間戳記和有效期間等資訊加密,產生一個有時效性的加密字串(EncryptedSceneId)。

  2. 前端初始化驗證碼:前端從商務服務端擷取該加密字串,並在初始化驗證碼時將其作為參數傳遞。

  3. 驗證碼服務校正:驗證碼服務端接收到請求後,將使用在控制台開啟的加密模式進行強制解密和安全校正。校正失敗的請求將被拒絕。

步驟一:服務端產生加密字串

擷取密钥(ekey)

登入阿里雲驗證碼控制台,在概览頁面右上方地區,擷取用於加密的密钥(ekey)

構造待加密資料並執行加密

  1. 構造明文資料字串。

    • 資料格式:

      sceneId&timestamp&expireTime
    • 參數說明:

      • sceneId (String):驗證碼情境的唯一標識(原 CaptchaAppid)。

      • timestamp (Long):目前時間的 Unix 時間戳記(單位:秒)。

        說明

        此時間戳記不得晚於伺服器接收請求的時間。

      • expireTime (Int):加密字串的有效時間長度(單位:秒),取值範圍為 1 至 86400(即 1秒 至 24小時)。

    • 資料樣本:

      sdewfwe&1712345678&3600
  2. 執行 AES-256 加密。

    使用以下參數對上一步的明文字串進行 AES 加密,得到加密後的位元組數組( CaptchaSceneIdEncrypted)。

    • 密碼編譯演算法:AES

    • 模式:CBC

    • 填充方式:PKCS7Padding

    • 密鑰(Key):使用前面步驟擷取的密钥(ekey)

    • 初始化向量(IV):一個隨機產生的 16 位元組數組。

      重要

      為確保安全性,每次加密操作都必須使用一個全新的、通過密碼學安全隨機數產生器產生的 IV。嚴禁複用 IV。

  3. 拼接並進行 Base 64 編碼。

    1. 將 16 位元組的 IV 與 AES 加密後的密文位元組數組按順序拼接。拼接格式:IV + CaptchaSceneIdEncrypted,中間無串連符。

    2. 對拼接後的位元組數組進行 Base 64 編碼(標準編碼,不含分行符號)。

    3. 最終得到的字串即為傳遞給前端的 EncryptedSceneId

服務端程式碼範例 (Java)

以下樣本展示了如何產生 EncryptedSceneId

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Base64;

public class SceneIdEncryptor {

    // IV 固定為 16 位元組 (AES block size)
    private static final int IV_LENGTH_BYTES = 16;

    /**
     * 對 sceneId 進行加密,產生 EncryptedSceneId
     *
     * @param sceneId   驗證碼業務標識(原 CaptchaAppid)
     * @param ekeyStr   控制台擷取的 ekey(作為密鑰基礎)
     * @param expireTimeSec  密文到期時間,單位秒,範圍 1~86400
     * @return Base64   編碼的字串:EncryptedSceneId(IV + 加密資料)
     * @throws Exception   加密過程中可能出現異常(如不支援的演算法)
     */
    public static String encryptSceneId(String sceneId, String ekeyStr, int expireTimeSec) throws Exception {
        if (expireTimeSec <= 0 || expireTimeSec > 86400) {
            throw new IllegalArgumentException("expireTimeSec must be between 1 and 86400 seconds.");
        }

        // 擷取目前時間戳(秒級)
        long timestamp = Instant.now().getEpochSecond();

        // ==================== 步驟 1: 構造密鑰 Key(32位元組) ====================
        byte[] keyBytes = Base64.getDecoder().decode(ekeyStr);

        // ==================== 步驟 2: 構造明文並執行 AES-256-CBC 加密 ====================
        // 明文格式:sceneId&timestamp&expireTime
        String plaintext = sceneId + "&" + timestamp + "&" + expireTimeSec;
        byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);

        // 建立 Cipher 執行個體:AES/CBC/PKCS7Padding
        // 注意:Java 預設使用 PKCS5Padding,但其與 PKCS7 在 AES 中等價(塊大小相同)
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // Java 不直接支援 PKCS7Padding,但行為一致

        // 隨機產生 16 位元組 IV
        SecureRandom random = new SecureRandom();
        byte[] iv = new byte[IV_LENGTH_BYTES];
        random.nextBytes(iv); // 安全隨機填充 IV
        IvParameterSpec ivSpec = new IvParameterSpec(iv);

        // 使用 32 位元組密鑰初始化 SecretKeySpec
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");

        // 初始化 Cipher 為加密模式
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec);

        // 執行加密,得到密文位元組數組(即 CaptchaSceneIdEncrypted)
        byte[] encryptedBytes = cipher.doFinal(plaintextBytes);

        // ==================== 步驟 3: 拼接 IV + 密文,並進行 Base 64 編碼 ====================
        // 建立一個新的位元組數組:前 16 位元組是 IV,後面是加密內容
        byte[] result = new byte[IV_LENGTH_BYTES + encryptedBytes.length];
        System.arraycopy(iv, 0, result, 0, IV_LENGTH_BYTES);
        System.arraycopy(encryptedBytes, 0, result, IV_LENGTH_BYTES, encryptedBytes.length);

        // Base 64 編碼,得到最終的 EncryptedSceneId
        return Base64.getEncoder().encodeToString(result);
    }

    // ==================== 測試樣本 ====================

    public static void main(String[] args) {
        try {
            String sceneId = "s123456789"; // 替換為你的實際 sceneId
            String ekey = "your_key"; // 替換為控制台擷取的實際 AppSecretKey
            int expireTimeSec = 3600; // 設定期望的有效期間
            String encryptedSceneId = encryptSceneId(sceneId, ekey, expireTimeSec);
            
            System.out.println("EncryptedSceneId: " + encryptedSceneId);
            System.out.println("Length: " + encryptedSceneId.length() + " characters");

            // 樣本輸出(每次運行不同,因 IV 隨機):
            // EncryptedSceneId: YmFzZTY0RW5jb2RlZFN0cmluZzEyczM0dTV2Nnd4eXo=
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

步驟二:前端整合

前端需先從商務服務端介面擷取 EncryptedSceneId,然後在初始化驗證碼時傳入該參數。

參數說明

參數

類型

是否必填

預設值

描述

EncryptedSceneId

String

加密後的 SceneId。建立驗證情境後可獲得原始 SceneId。使用控制台頒發的密钥(ekey),按照文檔中的加密流程對該 SceneId 進行加密,最終得到的加密字串即為加密後的 SceneId。

前端程式碼範例 (JavaScript)

// 1. 定義一個從您的業務後端擷取加密字串(EncryptedSceneId)的函數
const getEncryptedSceneId = async () => {
  // 此處應調用您自己實現的後端介面
  const response = await fetch('/api/encrypt-scene-id', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
  });
  // 假設後端返回JSON資料為:{ "EncryptedSceneId": "..." }
  const { EncryptedSceneId } = await response.json();
  return EncryptedSceneId;
};

// 2. 在初始化驗證碼時傳入 EncryptedSceneId
const initCaptcha = async () => {
    const encryptedId = await getEncryptedSceneId();
    // 調用阿里雲驗證碼初始化函數
    window.initAliyunCaptcha({
      // 情境ID。在建立驗證情境後,可在驗證碼情境列表擷取情境ID
      SceneId: "******",
      EncryptedSceneId: encryptedId,
      // ... 其他初始化參數
    });
};

// 執行初始化
initCaptcha();

步驟三:在控制台開啟校正

在完成前後端代碼的開發與測試後,可在驗證碼控制台為相應情境開啟加密模式

  1. 登入阿里雲驗證碼控制台,單擊左側導覽列的场景管理

  2. 场景管理頁面,找到需要開啟加密模式的目標情境,啟用加密模式列下的開關。

開啟後,驗證碼服務端將對所有傳入 EncryptedSceneId 的請求執行以下強制校正:

  • 解密是否成功。

  • sceneId 是否合法。

  • timestamp 是否在允許的時間視窗內(防止重放攻擊)。

  • 加密字串是否在 expireTime 定義的有效期間內。

說明

任何一項校正失敗都會導致請求被拒絕。請務必在開啟此開關前,確保您的服務端加密邏輯和前端整合已正確實現並完成測試。