Skip to content

AES 加密與失敗處理

概述

CDR 推送支援 AES 加密和簽名驗證機制,確保數據傳輸的安全性和完整性。同時實現了智能重試機制,確保 CDR 數據的可靠交付。

AES 加密機制

加密概述

  • 演算法:AES-128-CBC
  • 密鑰長度:128 bit (16 字節)
  • 初始化向量 (IV):128 bit (16 字節),通常為隨機生成
  • 填充方式:PKCS7

加密流程

步驟 1:準備數據

原始 CDR JSON 數據:

json
{
  "call": {
    "id": "call_20260323_001",
    "callType": 2,
    "duration": 150,
    ...
  },
  ...
}

步驟 2:生成 IV

隨機生成 16 字節的初始化向量:

IV = random_bytes(16)

步驟 3:AES 加密

使用提供的密鑰和 IV 對 JSON 字符串進行 AES-128-CBC 加密:

encrypted_data = AES_Encrypt(json_string, key, iv)

步驟 4:組裝加密包

encrypted_payload = base64(iv) + ":" + base64(encrypted_data)

HTTP 推送格式

加密後的 CDR 通過以下格式推送:

http
POST {webhookUrl}
Content-Type: application/json
X-AICC-Signature: {signature}
X-AICC-Timestamp: {timestamp}

請求體:

json
{
  "encrypted": true,
  "encryptionType": "AES-128-CBC",
  "payload": "{base64_iv}:{base64_encrypted_data}"
}

Python 解密示例

python
import base64
from Crypto.Cipher import AES
import json

def decrypt_cdr(encrypted_payload, key):
    """
    解密 CDR 數據

    Args:
        encrypted_payload: 格式為 "base64_iv:base64_encrypted_data" 的字符串
        key: AES 密鑰 (16 字節)

    Returns:
        解密後的 CDR 字典
    """
    # 分割 IV 和加密數據
    parts = encrypted_payload.split(':')
    if len(parts) != 2:
        raise ValueError("Invalid encrypted payload format")

    iv = base64.b64decode(parts[0])
    encrypted_data = base64.b64decode(parts[1])

    # 解密
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted = cipher.decrypt(encrypted_data)

    # 移除 PKCS7 填充
    padding_length = decrypted[-1]
    decrypted = decrypted[:-padding_length]

    # 解析 JSON
    cdr_data = json.loads(decrypted.decode('utf-8'))
    return cdr_data

# 使用示例
cdr_key = b'your_aes_key_16bytes'  # 16 字節的密鑰

@app.route('/cdr/webhook', methods=['POST'])
def handle_cdr():
    data = request.get_json()

    if data.get('encrypted'):
        payload = data.get('payload')
        cdr_data = decrypt_cdr(payload, cdr_key)
    else:
        cdr_data = data

    # 處理 CDR 數據
    print(cdr_data)
    return {'code': 0, 'message': 'success'}

Node.js 解密示例

javascript
const crypto = require('crypto');

function decryptCdr(encryptedPayload, key) {
  // 分割 IV 和加密數據
  const parts = encryptedPayload.split(':');
  if (parts.length !== 2) {
    throw new Error('Invalid encrypted payload format');
  }

  const iv = Buffer.from(parts[0], 'base64');
  const encrypted = Buffer.from(parts[1], 'base64');

  // 解密
  const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
  let decrypted = decipher.update(encrypted);
  decrypted = Buffer.concat([decrypted, decipher.final()]);

  // 移除 PKCS7 填充
  const paddingLength = decrypted[decrypted.length - 1];
  decrypted = decrypted.slice(0, decrypted.length - paddingLength);

  // 解析 JSON
  const cdrData = JSON.parse(decrypted.toString('utf-8'));
  return cdrData;
}

// 使用示例
app.post('/cdr/webhook', (req, res) => {
  try {
    if (req.body.encrypted) {
      const key = Buffer.from('your_aes_key_16bytes');
      const cdrData = decryptCdr(req.body.payload, key);
      console.log(cdrData);
    } else {
      console.log(req.body);
    }

    res.json({ code: 0, message: 'success' });
  } catch (error) {
    console.error('Decryption error:', error);
    res.status(500).json({ code: 500, message: 'error' });
  }
});

Java 解密示例

java
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import org.json.JSONObject;

public class CDRDecryptor {

    public static JSONObject decryptCdr(String encryptedPayload, byte[] key)
            throws Exception {
        // 分割 IV 和加密數據
        String[] parts = encryptedPayload.split(":");
        if (parts.length != 2) {
            throw new IllegalArgumentException("Invalid encrypted payload format");
        }

        byte[] iv = Base64.getDecoder().decode(parts[0]);
        byte[] encrypted = Base64.getDecoder().decode(parts[1]);

        // 初始化 AES Cipher
        SecretKeySpec keySpec = new SecretKeySpec(key, 0, key.length, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

        // 解密
        byte[] decrypted = cipher.doFinal(encrypted);
        String jsonString = new String(decrypted, "UTF-8");

        // 解析 JSON
        return new JSONObject(jsonString);
    }

    // 使用示例
    public static void main(String[] args) throws Exception {
        byte[] key = "your_aes_key_16bytes".getBytes();
        String encryptedPayload = "...";

        JSONObject cdrData = decryptCdr(encryptedPayload, key);
        System.out.println(cdrData.toString());
    }
}

簽名驗證

簽名算法

簽名用於驗證 CDR 推送的完整性和來源,防止數據被篡改。

signature = SHA256(payload + secretKey + timestamp)

其中:

  • payload 是 HTTP 請求體的完整內容 (JSON 字符串)
  • secretKey 是配置的 Webhook 密鑰
  • timestamp 是請求時間戳

Python 簽名驗證

python
import hashlib
import json

def verify_signature(raw_body, signature, timestamp, secret_key):
    """驗證 CDR 推送簽名"""
    message = raw_body + secret_key + timestamp
    expected_signature = hashlib.sha256(message.encode()).hexdigest()
    return signature == expected_signature

@app.route('/cdr/webhook', methods=['POST'])
def handle_cdr():
    signature = request.headers.get('X-AICC-Signature')
    timestamp = request.headers.get('X-AICC-Timestamp')
    raw_body = request.get_data(as_text=True)

    secret_key = "your_webhook_secret_key"

    if not verify_signature(raw_body, signature, timestamp, secret_key):
        return {'code': 401, 'message': 'Invalid signature'}, 401

    # 簽名驗證成功,處理 CDR
    return {'code': 0, 'message': 'success'}

CDR 推送失敗重試機制

重試策略

當業務系統推送 CDR 失敗時,AICC 系統會自動按照以下策略進行重試:

重試次數延遲時間累計延遲說明
1立即0 秒首次發送
22 秒2 秒第 1 次重試
34 秒6 秒第 2 次重試
48 秒14 秒第 3 次重試
516 秒30 秒第 4 次重試
632 秒62 秒第 5 次重試
764 秒126 秒第 6 次重試
8128 秒254 秒第 7 次重試
9256 秒510 秒第 8 次重試
10512 秒1022 秒第 9 次重試

重試停止條件

重試會在以下任何條件滿足時停止:

  1. 成功推送:收到 HTTP 200-299 狀態碼且回應 code: 0
  2. 達到最大重試次數:共進行 10 次嘗試 (1 次初始 + 9 次重試)
  3. 永久錯誤:收到 4xx 狀態碼 (除 408, 429 外),表示業務邏輯錯誤,不重試

重試失敗處理

當所有重試都失敗後:

  1. 記錄失敗日誌:系統記錄失敗的 CDR 及原因
  2. 告警通知:向管理員發送告警通知
  3. 人工介入:管理員可通過後台重新手動觸發推送
  4. 數據保留:失敗的 CDR 數據保留 30 天,可通過後台查詢

重試失敗原因分析

常見的推送失敗原因及解決方案:

原因狀態碼說明解決方案
連接超時連接失敗網絡問題或服務器無響應檢查網絡連接和服務器狀態
請求超時408請求超時 (將重試)優化後端處理性能
服務不可用503服務器維護或過載 (將重試)等待服務器恢復
非法簽名401簽名驗證失敗檢查密鑰配置是否正確
請求錯誤400請求格式或內容錯誤檢查 CDR 數據格式
限流429請求過於頻繁 (將重試)檢查 Webhook 配置的速率限制
內部錯誤500業務系統內部錯誤 (不重試)檢查業務系統日誌

CDR 推送配置

Webhook 配置參數

json
{
  "webhookUrl": "https://api.example.com/cdr/webhook",
  "secretKey": "your_webhook_secret_key",
  "encryptionKey": "your_aes_key_16bytes",
  "encryptionEnabled": true,
  "maxRetries": 10,
  "retryInterval": "exponential",
  "enabled": true
}

配置說明

參數名類型必填說明
webhookUrlstringCDR 推送地址 (必須 HTTPS)
secretKeystring用於簽名驗證的密鑰
encryptionKeystringAES 加密密鑰 (16 字節)
encryptionEnabledboolean是否啟用加密,預設為 false
maxRetriesinteger最大重試次數,預設為 10
retryIntervalstring重試間隔策略 (exponential 指數級,預設值)
enabledboolean是否啟用此 Webhook,預設為 true

最佳實踐

安全建議

  1. HTTPS 加密傳輸:所有 Webhook URL 必須使用 HTTPS
  2. 密鑰管理:定期更換 secretKey 和 encryptionKey,避免洩露
  3. 簽名驗證:始終驗證 X-AICC-Signature,防止非法請求
  4. 速率限制:根據服務能力配置合理的 Webhook 速率限制
  5. 日誌記錄:記錄所有接收的 CDR,便於問題追蹤

性能建議

  1. 異步處理:CDR 接收後應放入隊列,異步處理避免阻塞
  2. 快速應答:快速返回 200 OK,不要在 Webhook 中進行耗時操作
  3. 冪等設計:實現冪等性,重複接收同一 CDR 應返回相同結果
  4. 數據校驗:驗證 CDR 數據完整性,檢查必填字段

監控告警

  1. 推送失敗監控:監控 Webhook 推送失敗率
  2. 延遲監控:監控 CDR 推送到接收的延遲時間
  3. 告警規則:當失敗率超過閾值時發出告警
  4. 日誌分析:定期分析失敗日誌,發現問題根源

常見問題

Q1: 如何修改加密密鑰?

修改後的推送將使用新密鑰加密。建議先部署能支持新密鑰解密的代碼,再修改配置。

Q2: 什麼情況下不會重試?

HTTP 500、502 等伺服器錯誤不會重試 (視為非暫時性錯誤)。

Q3: 如何查看推送失敗的 CDR?

可在 AICC 後台「系統設定」>「Webhook」>「失敗日誌」中查看。

Q4: 是否支援自定義重試策略?

目前不支持自定義重試策略,使用固定的指數級退避策略。

承暉資訊資源中心