← 所有工具
我們的筆記

CyberChef 手動驗證 X-Device-Signature

webhook 簽章對不上時,第一個要判斷的是哪一邊錯了——你的還是我們的。CyberChef 能讓你在瀏覽器裡跑 UnifyPort 完全一樣的簽章演算法,用 handler 看到的同一組輸入,三十秒內出結果。

UnifyPort 到底簽什麼

每次投遞只要設定了簽章密鑰,就會帶三個 header:

X-Device-Delivery-Id: d_01J2K…
X-Device-Timestamp: 1716800000
X-Device-Signature: 9f8c…

簽章按以下方式計算:

X-Device-Signature = hex( HMAC-SHA256( secret, timestamp + "." + raw_body ) )

偵錯前貼幾條在螢幕邊:

  • timestamp 和 body 中間的那個字元是 ASCII 點號(.),不是換行。
  • raw_body 指我們送過去的原始位元組——在你這邊做任何 JSON 解析、去空白、unicode 正規化之前
  • hex 輸出是小寫。如果跟大寫 digest 比對,會假陰性。

CyberChef recipe

在 CyberChef 裡按順序把兩個操作丟進 recipe 面板:

  1. 1. HMAC——把 Hashing function 設成 SHA256。把你的簽章密鑰貼進 KeyTypeUTF8
  2. 2. To Hex——Delimiter 留空,Bytes per line0。這樣能得到沒有空白的小寫 hex 字串,跟 header 對得上。

Input 面板貼上的內容必須正好是 <timestamp>.<raw_body>——例如:

1716800000.{"id":"evt_demo","type":"message.received",...}

Output 面板出來的應該跟那條投遞裡的 X-Device-Signature 一致。如果不一致,bug 鎖定在三處之一:用了不同的 secret、body 在途中被改過、或者你讀的是錯的 timestamp header。

這種手動驗證真正派上用場的時刻

  • 輪換簽章密鑰之後。在你怪 DNS 或部署沒重新整理之前,先確認新密鑰真的到了 handler。
  • 升級 JSON 函式庫之後。有些解析器在交給驗證邏輯前重新輸出過 body——這種事很容易漏看,直到簽章突然對不上。
  • 隊友說「我這邊跑得起來」的時候。兩個人各跑同一個 recipe、同一組輸入;digest 不一樣的那個就是拿了錯的 secret。

把密鑰貼進網頁這件事

CyberChef 是純靜態的客戶端 bundle——完全在你瀏覽器裡跑,官方代管版本裡 secret 不會離開你的機器。話雖如此,如果你擔心瀏覽器擴充功能或共用工作階段,可以在本地跑 CyberChef(clone 倉庫後開啟 index.html)或者用 DevToys——完全離線的桌面端等價物。正式環境的簽章密鑰輪換,要走程式碼做 constant-time 比對,而不是用肉眼看 CyberChef 輸出。

常見問題

我要怎麼手動驗證 UnifyPort 的 webhook 簽章?
在 CyberChef 裡,加上 HMAC(Hashing function 選 SHA256,把你的簽章密鑰當 Key、編碼用 UTF8),接著加 To Hex(Delimiter 留空,Bytes per line 設 0)。把 <timestamp>.<raw_body> 原封不動貼進 Input 面板。Output 應該跟 X-Device-Signature header 逐位元組一致。
為什麼我的簽章跟 UnifyPort 送來的對不上?
三個常見原因:(1) 你用了錯的簽章密鑰——確認它的值跟你在控制台看到的一致;(2) body 在驗證前被改過——重新輸出的 JSON、去空白、或 unicode 正規化都會破壞 digest;(3) timestamp 讀錯了 header——只有 X-Device-Timestamp 有被簽章,框架的 Date header 沒有。
把簽章密鑰貼進 CyberChef 安全嗎?
官方代管的 CyberChef 是純靜態的客戶端 bundle——secret 不會離開你的瀏覽器,也沒有伺服器端的記錄。如果你還是想做到零暴露,可以 clone 倉庫後在本地開啟 index.html,或者用 DevToys——完全離線的桌面端等價物。

手動驗過之後

把等價邏輯搬進你的 handler。大多數語言的標準函式庫就有原語:Node 的 crypto.createHmac、Python 的 hmac.new、Go 的 hmac.New。比對一定要用 constant-time 函式(timingSafeEqualhmac.compare_digesthmac.Equal)——絕對不要用 ==