加密模式是一項可選的安全增強功能。啟用後,您的服務端需使用密钥(ekey)動態產生加密字串,並傳遞給前端用於驗證碼初始化。該模式可有效防止因 sceneId 泄露而導致的服務盜用或惡意調用,從而提升介面安全性。
本文僅適用於V3架構接入。V2架構下,驗證介面由使用者的服務端發起,無需開啟加密模式。
工作原理
服務端產生加密字串:商務服務端使用從驗證碼控制台擷取的密钥(ekey),將情境ID(
sceneId)、時間戳記和有效期間等資訊加密,產生一個有時效性的加密字串(EncryptedSceneId)。前端初始化驗證碼:前端從商務服務端擷取該加密字串,並在初始化驗證碼時將其作為參數傳遞。
驗證碼服務校正:驗證碼服務端接收到請求後,將使用在控制台開啟的加密模式進行強制解密和安全校正。校正失敗的請求將被拒絕。
步驟一:服務端產生加密字串
擷取密钥(ekey)
登入阿里雲驗證碼控制台,在概览頁面右上方地區,擷取用於加密的密钥(ekey)。
構造待加密資料並執行加密
構造明文資料字串。
資料格式:
sceneId×tamp&expireTime參數說明:
sceneId(String):驗證碼情境的唯一標識(原CaptchaAppid)。timestamp(Long):目前時間的 Unix 時間戳記(單位:秒)。說明此時間戳記不得晚於伺服器接收請求的時間。
expireTime(Int):加密字串的有效時間長度(單位:秒),取值範圍為1至86400(即 1秒 至 24小時)。
資料樣本:
sdewfwe&1712345678&3600
執行 AES-256 加密。
使用以下參數對上一步的明文字串進行 AES 加密,得到加密後的位元組數組( CaptchaSceneIdEncrypted)。
密碼編譯演算法:AES
模式:CBC
填充方式:PKCS7Padding
密鑰(Key):使用前面步驟擷取的密钥(ekey)。
初始化向量(IV):一個隨機產生的 16 位元組數組。
重要為確保安全性,每次加密操作都必須使用一個全新的、通過密碼學安全隨機數產生器產生的 IV。嚴禁複用 IV。
拼接並進行 Base 64 編碼。
將 16 位元組的 IV 與 AES 加密後的密文位元組數組按順序拼接。拼接格式:
IV+CaptchaSceneIdEncrypted,中間無串連符。對拼接後的位元組數組進行 Base 64 編碼(標準編碼,不含分行符號)。
最終得到的字串即為傳遞給前端的
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×tamp&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,然後在初始化驗證碼時傳入該參數。
參數說明
參數 | 類型 | 是否必填 | 預設值 | 描述 |
| 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();步驟三:在控制台開啟校正
在完成前後端代碼的開發與測試後,可在驗證碼控制台為相應情境開啟加密模式。
登入阿里雲驗證碼控制台,單擊左側導覽列的场景管理。
在场景管理頁面,找到需要開啟加密模式的目標情境,啟用加密模式列下的開關。
開啟後,驗證碼服務端將對所有傳入 EncryptedSceneId 的請求執行以下強制校正:
解密是否成功。
sceneId是否合法。timestamp是否在允許的時間視窗內(防止重放攻擊)。加密字串是否在
expireTime定義的有效期間內。
任何一項校正失敗都會導致請求被拒絕。請務必在開啟此開關前,確保您的服務端加密邏輯和前端整合已正確實現並完成測試。