多重要素驗證免費解決方案 - 使用微軟及 Google Authenticator App
9 | 14,371 |
這年頭,多重要素驗證(Mutli-Factor Authentication, MFA)幾乎已成系統安全管控的基本要求,除了帳號密碼之外,使用者必須再提供手機簡訊、身份驗證 App、指紋/瞳孔/臉部識別、IC 卡、USB Key... 等第二項驗證資訊才算登入成功,相較過去密碼一旦外流就被整碗端走,多一層驗證就是多一分保障。
這麼多方法中,驗證器 App 應是最易取得成本最低的選項,不像 IC 卡或 USB Key 需採購硬體設備,指紋或臉部識別需要手機或筆電硬體支援,發送簡訊要串第三方服務且有簡訊費用,使用者只要有手機或平板,裝個 Authenticator App 就能在密碼之外多加一道關卡,安全性大增。
在手機 App 商店搜尋 Authenticator 關鍵字,可以找到許多驗證器 App,Google 跟微軟也有推出自己的版本,除了整合自家登入系統也支援 TOTP (Time-based One-Time Password) 這類以時間為基準的一次性密碼,TOTP 為業界公開標準,只要 App 依循標準實作就可以用,但微軟或 Google 的 App 還是較易獲得一般人的信賴,自然是首選,本篇文章也會以這兩個 App 示範。
開始前,先簡單介紹網站或系統整合 TOTP 當成第二重驗證的流程:
- 使用者先以帳號密碼登入系統,選擇啟用一次性密碼作為第二重認證,系統隨機產生一組專屬 Secret (一串 20 個位元組的資料),並加上系統名稱(Issuer)、使用者識別資訊(Label)產生 QR Code。
- Authenticator App 掃瞄該 QR Code 取得 Secret,之後便能依據該 Secret 每 30 秒產生一組數字密碼,每組密碼有效期間 30 秒。
補充:依據 TOTP 演算邏輯,相同的 Secret 在某年某月某日某個時間的密碼值也會相同,因此只要時間準確且演算法一致,不管哪一支手機、哪個 App、哪台伺服器,算出來的密碼都應該是相同的。 - 使用者下次登入時,除了輸入帳號密碼,還需打開 Authenticator App 查詢當時的一次性密碼,伺服器依當下時間推算對映的密碼值加以比對,若不一致就拒絕登入,以達到雙重認證效果。
由以上運作原理,歸納資安重點:
- TOTP Secret 需每個人不同,系統應妥善保存及傳輸,嚴防外流,一旦遭竊第二重驗證將形同虛設。
- Authenticator App 必須儲存 TOTP Secret,也是關鍵資料外流的來源之一,這也是為什麼 Authenticator App 百百種,優先選微軟或 Google 的理由。
- Secret 唯一的一次傳輸只會發生掃 QR Code 時,不像一般帳號的密碼每次登入都會使用,甚至怕忘記寫在紙上,加上使用者不知道 Secret 內容,外流機率比一般密碼低很多。也因此處理 QR Code 就得格外小心,建議由程式在記憶體即時產生顯示,用完就消失,不宜用 Email 傳送也不要殘留檔案備份會比較安全。
- Authenticator App 可能故障或被刪除、手機可能遺失或重灌,除提醒使用者使用 App 功能備份外,也要預想備援的認證方式。
講完原理,到了令人興奮的實際操作時間。
下面照片中,左邊是微軟的 Authenticator,右邊是 Google 的,都有掃瞄 QR Code 的選項;
接下來我會用 .NET 產生 TOTP Secret 並轉成 QR Code,匯入微軟 Authenticator 後測試一次性密碼驗證,若整套流程能順利運作,將來即可搬進網站,為我們的系統加上多重要素驗證。
程式會用到兩個 NuGet 套件 - Otp.NET、QRCoder,不囉嗦,直接上程式:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using OtpNet;
using QRCoder;
namespace MSAuthenticatorTest
{
class Program
{
static void Main(string[] args)
{
//產生一組 Secret
var secret = KeyGeneration.GenerateRandomKey();
var sd = new SecretData
{
Issuer = "Darkthread",
Label = "TOTP測試",
Secret = Base32Encoding.ToString(secret)
};
//產生 QRCode
//補充說明:此處為求簡便寫成暫存檔以瀏覽器開啟,實際應用時宜全程於記憶體處理資料不落地
//並於網頁顯示完即銷毁,勿以 Email 或其他方傳遞,以降低外流風險
var qrCodeImgFile = System.IO.Path.GetTempFileName() + ".png";
sd.GenQRCode().Save(qrCodeImgFile, System.Drawing.Imaging.ImageFormat.Png);
//使用預設圖片檢視軟體開啟
var p = Process.Start(new ProcessStartInfo()
{
FileName = $"file:///{qrCodeImgFile}",
UseShellExecute = true
});
while (true)
{
Console.WriteLine("輸入一次性密碼進行驗證,或直接按 Enter 結束");
var pwd = Console.ReadLine();
if (string.IsNullOrEmpty(pwd)) break;
Console.WriteLine(" " + sd.ValidateTotp(pwd));
}
}
public class SecretData
{
public string Issuer { get; set; }
public string Label { get; set; }
public string Secret { get; set; }
public string GenQRCodeUrl() =>
$"otpauth://totp/{Label}?issuer={Uri.EscapeDataString(Issuer)}&secret={Uri.EscapeDataString(Secret)}";
public Bitmap GenQRCode()
{
var qrcg = new QRCodeGenerator();
var data = qrcg.CreateQrCode(GenQRCodeUrl(), QRCodeGenerator.ECCLevel.Q);
var qrc = new QRCode(data);
return qrc.GetGraphic(20);
}
Totp totpInstance = null;
public string ValidateTotp(string totp)
{
if (totpInstance == null)
{
totpInstance = new Totp(Base32Encoding.ToBytes(this.Secret));
}
long timedWindowUsed;
if (totpInstance.VerifyTotp(totp, out timedWindowUsed))
{
return $"驗證通過 - {timedWindowUsed}";
}
else
{
return "驗證失敗";
}
}
}
}
}
在不到 80 行的程式裡,我宣告了一個 SecretData 型別封裝 TOTP 相關邏輯,一開始用 KeyGeneration.GenerateRandomKey() 產生隨機 TOPT Secret,經過 Base32 編碼串接成一個特殊 URL otpauth://totp/label_name?issuer=issuer_name&secret=Base32編碼內容,這個 URL 可轉成如下 QR Code:
用微軟及 Google Authenticator 掃瞄這個 QR Code,兩個 App 會保存這組 TOTP Secret 並每 30 秒產生一組密碼,如前面所說,Secret 相同,密碼就會相同:
程式端驗證時,用 Secret 當參數建立 Totp 物件,呼叫 VerifyTotp() 可驗證密碼是否有效。VerifyTotp() 還有一個 out timedWindowUsed 參數,會標註此密碼所對映的 30 秒區間,如果要限定每個密碼只能使用一次,可在驗證完成後記下這筆 timedWindowUsed,如發現它被用來驗證第二次時予以拒絕。
就醬,下回老闆客戶嫌棄網站只用密碼管控不安全,要你學學微軟 Google 加上多重因素又不想多花半毛錢,知道該怎麼做了吧?除了遞辭呈,也可以挑戰寫幾行程式為網站增加一次式密碼驗證,讓系統安全提升一個檔次。
Tutorial of how to use Microsoft Authenticator app to add Time-based One-Time Password authenctation to implement MFA on your system.
Comments
# by Ms
參數digits與period都無作用?
# by Cojad
我也有一個30行 php 版本的 Google OTP實作, 歡迎參考使用
# by Cojad
我也有一個30行 php 版本的 Google OTP實作, 歡迎參考使用 https://gist.github.com/Cojad/71d1c9fc835eb225b075cb1a9cd6a780
# by Jeffrey
to Cojad, 好簡潔! 感謝分享。
# by Anderson
請問為什麼這一個範例產生的QR CODE在IOS上用微軟 Authenticator掃描要加入帳戶會發生QR CODE錯誤, 但是在ANDROID上就不會有問題
# by Jeffrey
to Anderson, iOS MS Authenticator 是只有掃這個範例的 QRCode 有問題還是所有 QRCode 都不行?用 Github 當對照組試試 https://docs.github.com/cn/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication
# by Dylan
iOS MS Authenticator 掃描時會發生錯誤是因為範例中的Label字串中有中文, Uri.EscapeDataString( Label )後再次掃描是可以正常使用。
# by 里克威斯特
發現錯字, 倒數第四行, 不是 VeriftyTotp() 而是 VerifyTotp()
# by Jeffrey
to 里克威斯特, 謝,已修正。