TOTP 驗證碼是怎麼算出來的

TOTP 驗證碼是一個 6 位數字,驗證器 App 與伺服器各自獨立算出,輸入只有兩個:同一把共享密鑰,和按 30 秒視窗切分的目前時間。驗證碼本身在設定時從不傳輸,也不攜帶任何密鑰資訊,但兩邊總能得到相同的數字——因為 TOTP 不是魔法也不是隨機數,它是對相同輸入套用同一套公開演算法(RFC 6238)。本文把計算從頭到尾走一遍,講清為什麼「驗證碼錯誤」幾乎總是時鐘問題,並給出幾條決定你的第二因素還能不能算第二因素的使用紀律。你可以在TOTP 產生器裡親手跑每一步——它在你的瀏覽器本地計算。

兩個輸入,一個驗證碼:密鑰與時鐘

開啟兩步驗證時,服務產生一把隨機密鑰——通常 20 位元組,以 Base32 字串展示給你,並為了方便編碼進 QR 碼。這把密鑰只存兩處:服務的資料庫和你的驗證器。從那一刻起,登入變成一次同步計算:兩邊取目前 Unix 時間,除以時間步長(幾乎所有服務都是 30 秒),把得到的計數器連同密鑰一起送進雜湊函數。誰拿出相配的輸出,誰就被視為持有密鑰。

這個設計的優雅之處在於它省略了什麼:沒有要下發的挑戰、沒有要投遞的驗證碼、沒有網路往返、沒有每次登入可供攔截的東西——「一次性」來自時鐘,因為計數器每 30 秒變一次,舊輸出隨即作廢。這也意味著整個系統只有兩樣東西能出問題:密鑰,和時間。幾乎每一次 TOTP 失敗、每一次 TOTP 被盜,都能追溯到這兩者之一。

計算過程,一步一步

計數器先寫成 8 位元組大端整數——一種雙方約定的定寬編碼。然後以密鑰為鍵,對這條訊息做 HMAC,使用約定的雜湊:預設 SHA-1,服務也可以選 SHA-256、SHA-512。HMAC 是帶密鑰的構造,而不是普通雜湊:只雜湊計數器的話,任何人拿到演算法都能複現驗證碼;而 HMAC 的輸出在沒有密鑰時不可偽造,也無法反推出密鑰。

摘要有 20 位元組以上,登入框要的是 6 位數字。兩者之間的橋是動態截斷:取摘要最後一個半位元組給出一個偏移,從該偏移讀 4 位元組並遮掉最高位(避免符號歧義),得到的 31 位元數對 10 的位數次方取模——標準 6 位就是除以一百萬——左側補零。這就是你輸入的驗證碼。下一個視窗從頭重算;驗證碼之間沒有鏈條,所以拍下來的碼在 30 秒後一文不值。

驗證碼被拒:通常是時鐘的錯

計數器由時間導出,所以產生方和伺服器必須對「現在是哪個 30 秒視窗」達成一致。多數伺服器會寬鬆處理:前後相鄰視窗的碼也接受,這樣能悄悄吸收小幅偏差。超出這個範圍,密鑰再正確,每個碼都是錯的,而失敗的樣子和輸錯一位數字毫無區別——乾脆拒絕,不給任何關於哪個輸入不一致的提示。

所以驗證器突然失靈時,第一個要查的永遠不是密鑰,而是產生驗證碼那台裝置的時鐘。手動設定的時間、快沒電的 CMOS 電池、坐完航班時區模式不對的手機——任何一個都能讓裝置漂移一分鐘以上。我們的TOTP 產生器顯示即時時鐘卡,並拿你的裝置與伺服器時間做比對,正是為此。開啟自動日期與時間,驗證碼幾乎總是立刻恢復——不用重置、不用換密鑰、不用提工單。

密鑰就是全部

剝掉儀式感,TOTP 的安全只剩一條事實:誰有密鑰,誰就永遠擁有你的第二因素,在任何地方,且服務無從分辨他的碼和你的碼。設定時掃的 QR 碼不是「某個碼的圖片」——它就是密鑰本體,加上帳號標籤和參數,格式化成 otpauth:// URI。它的截圖,就是你第二因素的一份永久副本。

正因如此,使用紀律比密碼還要嚴:密碼由伺服器核驗、可以雜湊儲存;TOTP 密鑰卻必須能被雙方以原文取回,無法雜湊消解,每一份拷貝都是活拷貝。主本只放在你的驗證器或加密的密碼管理器裡。永遠不要把密鑰輸進在伺服器上產生驗證碼的網站——你等於把一把永久鑰匙交給了陌生人;也永遠不要把它貼進聊天框,原因見下一節。

為什麼密鑰絕不能進聊天框

把 2FA 密鑰貼進聊天——客服會話、團隊頻道、AI 助手——感覺無害,因為那串字元看起來像亂碼,對話感覺是私密的。兩者都不對。聊天會被記錄、留存、同步到帳號下的每一台裝置、可搜尋,並在你無法控制的伺服器上處理;AI 聊天還要再加上訓練管線和長期留存。密鑰一落進去,你的第二因素就存在於為保存和索引文字而設計的系統裡了。

安全的模式是:密鑰只應存在於三類地方——服務的資料庫、你的驗證器或密碼管理器、以及你掌控的裝置上的本地計算——本站的TOTP 產生器正是這樣造的:密鑰只存頁面記憶體,重新整理即銷毀。如果密鑰已經被貼到了不該貼的地方,就當它已燒毀:到服務裡把兩步驗證關掉再打開,換一把新密鑰,重新綁定驗證器,順手在密碼外洩檢查裡查一下密碼本身。

TOTP 擋不住什麼

TOTP 能擋住密碼被盜、撞庫和資料庫外洩:光知道密碼不再夠用。它擋不住即時釣魚:一個把你的憑證轉發給真服務、再向你要驗證碼的假登入頁,可以在 30 秒視窗內把碼原樣轉發——驗證碼證明的是持有密鑰,而不是你在和真網站說話。工作階段 Cookie 被盜同樣能完全繞過它,因為驗證碼只校驗一次。

這正是通行密鑰(passkey)和硬體安全金鑰在高端場景取代 TOTP 的原因:它們把證明綁定到網站的來源(origin),中繼者無物可偷。TOTP 仍是強大而通用的基線——遠勝於繼承了電話網路弱點的簡訊驗證碼——但部署時要心裡有數:把密鑰當總鑰匙保護,備份碼單獨存放,保持時鐘準確,服務提供 passkey 時優先用 passkey。

來源

常見問題

能從我的驗證碼反推出密鑰嗎?

不能。每個驗證碼都是截斷後的 HMAC 輸出,而 HMAC 是單向構造:觀察再多驗證碼,也無法重建密鑰,或在沒有密鑰時預測未來視窗的碼。第二因素真正被偷走的途徑,是密鑰本身外洩(截圖、聊天、伺服器端產生器),以及在視窗內轉發新鮮驗證碼的即時釣魚。

為什麼服務會接受上一個和下一個驗證碼?

為了容錯。你的裝置和伺服器很少在視窗翻轉的那一秒完全一致,小幅時鐘偏差又很常見,所以多數伺服器把相鄰時間步的碼也一併接受。這把有效視窗從 30 秒放寬到約 90 秒,是刻意的可用性取捨——拍下來的碼在一分鐘後仍然無用。

SHA-1 都「被攻破」了,基於它的 TOTP 還安全嗎?

在這種構造裡安全。著名的 SHA-1 攻擊攻破的是抗碰撞性,那對簽章和憑證重要。HMAC 的安全性不依賴抗碰撞——它依賴帶密鑰構造表現得像偽隨機函數——目前不存在從觀察到的輸出恢復 TOTP 密鑰或偽造驗證碼的實際攻擊。服務仍可選擇 SHA-256;本站產生器三種都支援。