← 所有工具 用 CyberChef 手動驗證
我們的筆記
用 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. HMAC——把 Hashing function 設成
SHA256。把你的簽章密鑰貼進 Key,Type 選UTF8。 - 2. To Hex——Delimiter 留空,Bytes per line 設
0。這樣能得到沒有空白的小寫 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-Signatureheader 逐位元組一致。 - 為什麼我的簽章跟 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 函式(timingSafeEqual、hmac.compare_digest、hmac.Equal)——絕對不要用 ==。