在自己的系統中實現手機二階段驗證
現在的應用程式登入,特別是 Microsoft, Google, Facebook等大廠的網站SSO登入,常常設計有二階段驗證(2FA),而其中手機App二階段驗證是最常用的模式。主要是因為簡單、迅速、免費,對於用戶、開發人員、網站本身,都是一個很好的選擇。
這種透過手機App實現的二階段驗證,採用的常是一種稱為TOTP(Time-based One Time Password)的技術。簡單的說就是,用戶以先以正確的帳號密碼登入之後,必要時(或每次)再跳出一個驗證視窗,讓用戶輸入另一組隨機產生的N位數密碼,而該密碼會自動出現在你的手機(APP)上,且只能使用一次並有時間限制。
如此一來,即便用戶的帳號密碼不慎外洩,駭客也必須要有用戶的手機,才能登入或執行進階的功能。當然,如果每次都要這樣驗證,用戶肯定會覺得麻煩,所以這類的驗證,往往不會在每次登入時都進行,而是在底下幾種情境下被觸發:
- 用戶在不尋常的情境下(地區、IP、時間)登入
- 用戶要進行進階的行為(例如變更密碼)
觸發的時機或演算法因系統特性不同而異,但2FA做法的原理則大致相同,這一篇,我們就來看如何實現2FA。
概念
上圖是 Twilio 網站的 TOTP示意圖,現在有很多業者提供 Authenticator App來實現這個功能,我自己喜歡 Twilio 的 Authy Authenticator, 你可以從底下位置下載:
https://authy.com/download/
使用 Authenticator App 實現2FA 的典型流程如下:
- 網站針對已登入的用戶產生一個唯一的key值,並以這個Key值產生URL,進而生成QR Code
- 讓用戶以任何一款Authenticator App掃描該 QR Code,即可在該App上登記註冊。
- 掃描好QR Code完成註冊後,一般會先做一次六位數密碼驗證,以便於確認用戶有正確完成掃描註冊。
- 未來,任何需要二階段驗證的時機,網站後台都可以呼叫API產生六位數密碼,並讓用戶在手機上檢視由App產生的六位數密碼,若兩者相同,即可確認用戶身分正確無誤。
實作
程式碼其實一點都不難,而且有現成的NuGet套件可以使用,我整理好的 source code 這邊:
https://github.com/isdaviddong/TOTP2FA.git
其中,針對已登入的用戶,產生唯一一個key值的程式碼如下:
using OtpNet;
Console.WriteLine($"\n ------------生成QR Code---------------");
string label = "Label Name";
string issuer = "Your App Name";
string accountName = "user@example.com";
byte[] secretKey = KeyGeneration.GenerateRandomKey();
string keyAsBase32String = Base32Encoding.ToString(secretKey);
Console.WriteLine($"\n\n已自動生成 secretKey: '{keyAsBase32String}' ,應妥善保存於資料庫中 ");
我們使用了 OtpNet 套件,上面的 secretKey ,是OtpNet套件自動幫我們產生的,當然你也可以自己產生。請將該 secretKey 跟用戶名稱一齊保存起來即可。
未來,只要用該 secretKey 產生的六位數一次性密碼,就會跟用戶手機上產生的密碼同步。
要具體實現這個,你得先讓用戶掃描QR Code,可以透過底下這段程式碼,建立生成QR Code所需要的URL:
var totp = new Totp(secretKey);
string provisioningUri = $"otpauth://totp/{Uri.EscapeDataString(label)}:{accountName}?secret={keyAsBase32String}&issuer={Uri.EscapeDataString(issuer)}";
Console.Write($"使用 secretKey 產生的 URL : \n{provisioningUri}");
Console.WriteLine($"\n\n請掃描此 URL 產生的 QR Code... ");
上面這個以 otpauth://totp/ 開頭的URL,其實就是 QR Code,你只需要用坊間的 QR Code生成套件,即可針對該URL產生 QR Code。
用戶以手機上安裝好的Authenticator App掃描該QR Code之後,即可為你的網站在手機應用程式上,註冊出一個一次性密碼產生器。
一般來說,我們會先讓用戶完成註冊,隨時用app產生的六位數密碼進行第一次驗證,以確保用戶操作正確。
這部分你可以用底下這段Code來實現:
bool result = false;
while (result != true)
{
long TimeStep = 0;
Console.Write($"\n 輸入六位數一次性密碼 : ");
var Inputkey = Console.ReadLine();
result = totp.VerifyTotp(Inputkey, out TimeStep);
Console.Write($"驗證結果 : {result}");
}
Console.WriteLine($"\n 當驗證結果為 true, 未來,即可隨時使用同樣的 secretKey '{keyAsBase32String}' 建立totp物件,進行身分驗證 ");
通過上面這段 code 所產生的一次性密碼,如果符合用戶手機上app所產生的,那也就意味著,用戶成功的對齊(註冊)了我們的App。
這時你就可以把該用戶的email和secretKey 儲存起來,未來,任何需要驗證時候,你只需要用底下這段code,即可產生一組跟用戶手機app上所產生的一次性密碼相同的六位數密碼:
result = false;
Console.WriteLine($"\n ------------開始驗證---------------");
while (result != true)
{
long TimeStep = 0;
var totp2 = new Totp(secretKey);
Console.Write($"\n 輸入六位數一次性密碼 : ");
var Inputkey = Console.ReadLine();
result = totp2.VerifyTotp(Inputkey, out TimeStep);
Console.Write($"驗證結果 : {result}");
}
如此一來,即可驗證當前使用系統的用戶,確實擁有該用戶的手機(也就是說他是用戶本人)。這個功能,就程式碼來說,其實是非常簡單的。
留言