使用 TOTP 进行验证

为用户生成 secret, 根据规则创建二维码, 使用微软或是谷歌验证工具扫码登记。验证时由程序生成的 code 与验证工具生成的 code 进行比较。如果一致,则验证通过

https://blog.darkthread.net/blog/mfa-with-ms-authenticator/

<Query Kind="Program">
  <NuGetReference>Otp.NET</NuGetReference>
  <NuGetReference>QRCoder</NuGetReference>
  <Namespace>OtpNet</Namespace>
  <Namespace>QRCoder</Namespace>
  <Namespace>System.Drawing</Namespace>
</Query>

// 程式會用到兩個 NuGet 套件 - Otp.NET、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(){

                var secret = Uri.EscapeDataString(Secret);
                // var uriString = new OtpUri(OtpType.Totp, secret, this.Label, "ACME Co").ToString();
                var uriString = new OtpUri(OtpType.Totp, secret, this.Label, this.Issuer, OtpHashMode.Sha512).ToString();
                return uriString;

                // $"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 "驗證失敗";
                }
            }
        }
    }
}
上一篇
下一篇