簡訊驗證最佳實務:設計經得起考驗的 OTP 流程

Jan 16, 2026

簡訊一次性驗證碼是網路上部署最廣的第二因素,也是仍在大規模使用的最弱的那一個。NIST 自 2016 年起就把 SMS 列為受限的驗證方式,而它至今無所不在,只因為它是幾乎每個使用者都已經擁有的唯一因素。

如果你在建置或營運一套 OTP 流程,實際的問題不是「簡訊是不是理想方案」,而是「怎麼營運它,才能讓失敗模式是你自己選的那些」。本文談的是防禦性設計,不涉及繞開任何人的保護機制。

簡要威脅模型

請針對下面這些具體項目做設計,而不是籠統地防「詐騙」:

  • SIM 交換與號碼劫持 —— 攻擊者說服電信商把受害者的號碼轉到自己的裝置上。你的 OTP 流程偵測不到這件事,只有近期性訊號與升級驗證能發揮作用。
  • 即時釣魚中繼 —— 代理網站從受害者取得驗證碼,並在有效期內重放。這是目前針對簡訊 OTP 的主流攻擊,驗證碼生命週期是你對抗它的主要槓桿。
  • 裝置被入侵時的驗證碼竊取 —— 通知預覽、擁有簡訊權限的惡意應用、跨裝置同步的訊息紀錄。
  • 暴力破解與重放 —— 猜測 6 位數驗證碼,或重複使用已攔截的驗證碼。
  • 帳號列舉 —— 用 OTP 端點探測哪些手機號碼有帳號。
  • 簡訊灌送(話費詐欺) —— 攻擊者驅動大量發送到自己能分潤的高費率號段。這一項直接燒你的錢,而且通常直到帳單來了才被發現。

真正有效的限流

多數限流失效,是因為只在一個維度上生效,而攻擊者會輪換其他維度。

**同時依多個鍵限流。**依手機號碼、依帳號、依 IP、依裝置指紋,以及依目標國家的全域配額。輪換 IP 的攻擊者仍會撞上依號碼的上限;輪換號碼的會撞上依 IP 的上限。

**用漸進延遲,而不是一堵硬牆。**逐級退避——30 秒、2 分鐘、10 分鐘——能壓制自動化攻擊的吞吐量,同時不會把真的打錯了的使用者直接鎖死。

**依目標國家設上限。**簡訊灌送集中在特定號段。為每個國家設發送上限並在接近時告警,能在帳單變大之前抓到它。

**在異常時加摩擦,而不是依量加。**每次請求都上驗證碼,只會訓練使用者無視它;只在風險分數越過門檻時上,才保住了它的訊號價值。

**把發送限額與驗證限額分開。**這是兩種不同的攻擊。發送要花錢、會被灌送利用;驗證嘗試則是暴力破解。在少量失敗嘗試後(常見上限是 5 次)鎖死驗證碼,這與你允許多少次發送無關。

驗證碼的生命週期、熵與綁定

**保持短生命週期。**是分鐘級,不是小時級。這個視窗是你對抗即時釣魚中繼的主要防線:五分鐘過期的驗證碼,就是給攻擊者五分鐘。更短更安全,但短於兩分鐘左右,就會開始因為慢路由的使用者而產生客服壓力。

**一次性使用,重寄即失效。**使用者要求新驗證碼時,舊的必須立刻作廢。讓先前的驗證碼繼續有效,只是在毫無收益的情況下成倍放大攻擊面。

**至少 6 位數。**搭配 5 次嘗試上限,6 位數已經讓暴力破解不可行。4 位數驗證碼在寬鬆的嘗試預算下是猜得出來的。

**把驗證碼綁定到情境。**記錄它是為什麼簽發的,並在核銷時檢查:

  • 動作(註冊 vs 登入 vs 重設密碼 vs 付款確認)
  • 發起請求的工作階段或裝置
  • 正規化為 E.164 的號碼與地區

其中綁定動作最關鍵。一組用於確認變更信箱的驗證碼,不該能用來授權一筆轉帳。

在簡訊內文裡說明這組驗證碼是做什麼的。「123456 是您確認 400 美元轉帳的驗證碼」給了釣魚受害者一個察覺不對勁的機會。孤零零六個數字什麼也給不了。

投遞 UX:客服工單的來源

使用者對 OTP 的感受是二元的——不是好用,就是產品壞了。而這種感受大部分是介面塑造的,不是投遞決定的。

明確等待預期。「驗證碼通常在 30 秒內送達」能阻止重寄螺旋,而重寄螺旋本身會觸發限流,讓投遞真的變差。

**重寄按鈕搭配可見倒數。**一個帶計時的灰色按鈕,能阻止那個最傷使用者自己的行為。

**錯誤訊息不要洩漏帳號是否存在。**標準寫法是「如果該號碼有帳號,我們已寄出驗證碼」。區分「無此帳號」與「已寄出」,等於免費送給攻擊者一個列舉探針。

**支援貼上與自動填入。**輸入框加 autocomplete="one-time-code",簡訊內文採用綁定來源的 WebOTP 格式。把驗證碼拆成六個獨立小格子還禁止貼上,既是可用性問題也是無障礙問題。

**提供備援路徑。**語音電話、電子郵件,或引導啟用驗證器。某些電信商與地區的組合會靜默失敗,而一個沒有備選方案的使用者會直接離開。

**永遠不要顯示完整號碼。**任何確認畫面上都只顯示末 2 到 4 碼。

依地區與電信商做監測

彙總的投遞成功率這個數字,會掩蓋每一個值得發現的問題。

目標國家與電信商分別追蹤發送成功率、送達回條與驗證完成率。上游路由變更的表現形式是某一個地區塌陷、而全域平均值幾乎不動。告警應該基於各分段相對自身基準的偏離,而不是總數。

另一個值得追蹤的指標是驗證耗時分布。p95 上升通常代表某條路由在徹底失敗之前已經在劣化。

資料保留:少存

只保留營運客服與調查事件所必需的:

  • **該留:**時間戳記、投遞狀態、目標國家、號碼的雜湊或截斷參照,以及驗證結果的稽核軌跡。
  • **別留:**超出驗證視窗的、含有有效驗證碼的簡訊內文。營運上沒有理由,出事時全是負債。
  • **驗證碼落庫要雜湊。**明文躺在資料庫或日誌裡的驗證碼是純粹多餘的暴露。記錄它存在過,永遠不要記錄它的值。
  • **設定真正的 TTL。**永久保留的驗證紀錄,會替你累積出一個你本來沒打算建立的手機號碼資料集。

USPhoneGen 的做法:隱私權政策 · 服務條款

使用虛擬號碼的使用者

你會有相當一部分使用者以虛擬號碼或線上接碼來驗證——出於隱私、測試,或者人在國外。你的系統應該在保持安全的同時,不把他們預設當成有罪。

**明確決定你的線路類型政策,並把它說出來。**如果你封鎖 VoIP 號碼,就在錯誤訊息裡寫清楚。對一個確定性的政策拒絕回傳籠統的「驗證失敗」,會把使用者推進一個永遠不可能成功的重試迴圈,最後落到你的客服佇列裡。

**不要懲罰電信商延遲。**地區差異是正常的。逾時與重試邏輯應該容納慢路由,而不是把它當成詐欺訊號。

**把線路類型當成眾多輸入之一。**號碼歷史、IP 信譽、裝置訊號與頻率都帶有資訊。一刀切封鎖 VoIP 是一把鈍器——對付款流程是對的,對訂閱電子報通常是錯的。

**在關鍵時刻複查,而不是持續複查。**在提領或變更憑證時再驗一次是相稱的。對一個休眠帳號反覆複查並把它鎖死,是你失去無辜使用者的方式。

線路類型分類的原理,以及它為什麼會造成你的使用者回報的那些拒絕:Non-VoIP 與 VoIP 號碼

規劃離開簡訊的路徑

簡訊 OTP 是地板,不是終點。設計時就要留出遷移使用者的能力:

  • 在簡訊之外同時提供 TOTP 或 passkey,並在登入成功之後提示啟用,而不是在註冊時——註冊環節增加摩擦會傷轉換。
  • **絕不讓簡訊靜默覆蓋更強的因素。**如果使用者已有 passkey,把簡訊當成無條件備援,等於把他們的安全等級降回簡訊。
  • **把更換號碼當成高風險事件。**要求提供現有因素,並在新號碼可用於帳號救援之前設置冷卻期。

常見問題

OTP 應該多久過期?
2 到 10 分鐘,5 分鐘是個合理的預設值。更短能壓縮釣魚中繼視窗;太短則會讓慢路由上的使用者頻繁失敗。

應該允許多少次驗證嘗試?
每組驗證碼 5 次左右,然後作廢並要求重新寄送。同時依帳號單獨統計失敗次數,以捕捉分散式猜測。

我該封鎖 VoIP 號碼嗎?
取決於這個帳號能做什麼。牽涉付款或受 KYC 規範的,應該封鎖。面向大眾的一般產品,一刀切封鎖會趕走正當使用者——把線路類型與其他訊號一起權衡更合適。

單項價值最高的控制措施是什麼?
帶有依國家發送上限的多維度限流。它同時應對暴力破解、帳號列舉與話費詐欺。

2026 年還值得部署簡訊 OTP 嗎?
作為「什麼都沒有的使用者」的基準線,值得。作為你提供的最強因素,不值得。上線它的同時,給出通往 TOTP 或 passkey 的路徑。

怎麼阻止簡訊灌送?
依國家設發送上限並告警、封鎖你根本不服務的目標號段、對新帳號施加漸進摩擦。盯著依國家分的「每個驗證成功使用者的成本」——灌送最先在這裡露出馬腳。

參考資料

延伸閱讀

管理員

管理員

簡訊驗證最佳實務:設計經得起考驗的 OTP 流程 | USPhoneGen