码迷,mamicode.com
首页 > Web开发 > 详细

谷歌身份验证 asp.net core和go的实现

时间:2021-01-29 12:11:48      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:div   装包   counter   col   client   rem   rev   line   攻击   

一、Google Authenticator 基本概念

      Google Authenticator是谷歌推出的一款动态口令工具,旨在解决大家Google账户遭到恶意攻击的问题,在手机端生成动态口令后,在Google相关的服务登陆中除了用正常用户名和密码外,需要输入一次动态口令才能验证成功,此举是为了保护用户的信息安全。

       谷歌验证(Google Authenticator)通过两个验证步骤,在登录时为用户的谷歌帐号提供一层额外的安全保护。使用谷歌验证可以直接在用户的设备上生成动态密码,无需网络连接。其基本步骤如下:

  1.  使用google authenticator PAM插件为登录账号生成动态验证码。
  2.  手机安装Google身份验证器,通过此工具扫描上一步生成的二维码图形,获取动态验证码。

?    当用户在Google帐号中启用“两步验证”功能后,就可以使用Google Authenticator来防止陌生人通过盗取的密码访问用户的帐户。通过两步验证流程登录时,用户需要同时使用密码和通过手机产生的动态密码来验证用户的身份。也就是说,即使可能的入侵者窃取或猜出了用户的密码,也会因不能使用用户的手机而无法登录帐户。

       更多原理可以查看阅读“详解Google Authenticator工作原理”。

二、.NET 使用 Google Authenticator 

    通过 Nuget 下载 Google Authenticator 安装包,Google Authenticator 在 PC 端生成二维码、手机上生成验证码、 PC 端校验验证码,这些过程无需网络,只需要保证 PC 时间和手机时间正确一致即可。

  Google Authenticator 工具类代码如下(引用自 https://www.cnblogs.com/denuk/p/11608510.html):

    public class GoogleAuthenticator
    {
        private readonly static DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        private TimeSpan DefaultClockDriftTolerance { get; set; }
 
        public GoogleAuthenticator()
        {
            DefaultClockDriftTolerance = TimeSpan.FromMinutes(5);
        }
 
        /// <summary>
        /// Generate a setup code for a Google Authenticator user to scan
        /// </summary>
        /// <param name="issuer">Issuer ID (the name of the system, i.e. ‘MyApp‘), can be omitted but not recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format </param>
        /// <param name="accountTitleNoSpaces">Account Title (no spaces)</param>
        /// <param name="accountSecretKey">Account Secret Key</param>
        /// <param name="QRPixelsPerModule">Number of pixels per QR Module (2 pixels give ~ 100x100px QRCode)</param>
        /// <returns>SetupCode object</returns>
        public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, int QRPixelsPerModule)
        {
            byte[] key = Encoding.UTF8.GetBytes(accountSecretKey);
            return GenerateSetupCode(issuer, accountTitleNoSpaces, key, QRPixelsPerModule);
        }
 
        /// <summary>
        /// Generate a setup code for a Google Authenticator user to scan
        /// </summary>
        /// <param name="issuer">Issuer ID (the name of the system, i.e. ‘MyApp‘), can be omitted but not recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format </param>
        /// <param name="accountTitleNoSpaces">Account Title (no spaces)</param>
        /// <param name="accountSecretKey">Account Secret Key as byte[]</param>
        /// <param name="QRPixelsPerModule">Number of pixels per QR Module (2 = ~120x120px QRCode)</param>
        /// <returns>SetupCode object</returns>
        public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, byte[] accountSecretKey, int QRPixelsPerModule)
        {
            if (accountTitleNoSpaces == null) { throw new NullReferenceException("Account Title is null"); }
            accountTitleNoSpaces = RemoveWhitespace(accountTitleNoSpaces);
            string encodedSecretKey = Base32Encoding.ToString(accountSecretKey);
            string provisionUrl = null;
            //otpauth://totp/Google:yourname@gmail.com?secret=xxxx&issuer=Google
            provisionUrl = String.Format("otpauth://totp/{2}:{0}?secret={1}&issuer={2}", accountTitleNoSpaces, encodedSecretKey.Replace("=", ""), UrlEncode(issuer));
 
 
 
            using (QRCodeGenerator qrGenerator = new QRCodeGenerator())
            using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(provisionUrl, QRCodeGenerator.ECCLevel.M))
            using (QRCode qrCode = new QRCode(qrCodeData))
            using (Bitmap qrCodeImage = qrCode.GetGraphic(QRPixelsPerModule))
            using (MemoryStream ms = new MemoryStream())
            {
                qrCodeImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
 
                return new SetupCode(accountTitleNoSpaces, encodedSecretKey, String.Format("data:image/png;base64,{0}", Convert.ToBase64String(ms.ToArray())));
            }
 
        }
 
        private static string RemoveWhitespace(string str)
        {
            return new string(str.Where(c => !Char.IsWhiteSpace(c)).ToArray());
        }
 
        private string UrlEncode(string value)
        {
            StringBuilder result = new StringBuilder();
            string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
 
            foreach (char symbol in value)
            {
                if (validChars.IndexOf(symbol) != -1)
                {
                    result.Append(symbol);
                }
                else
                {
                    result.Append(% + String.Format("{0:X2}", (int)symbol));
                }
            }
 
            return result.ToString().Replace(" ", "%20");
        }
 
        public string GeneratePINAtInterval(string accountSecretKey, long counter, int digits = 6)
        {
            return GenerateHashedCode(accountSecretKey, counter, digits);
        }
 
        internal string GenerateHashedCode(string secret, long iterationNumber, int digits = 6)
        {
            byte[] key = Encoding.UTF8.GetBytes(secret);
            return GenerateHashedCode(key, iterationNumber, digits);
        }
 
        internal string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6)
        {
            byte[] counter = BitConverter.GetBytes(iterationNumber);
 
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(counter);
            }
 
            HMACSHA1 hmac = new HMACSHA1(key);
 
            byte[] hash = hmac.ComputeHash(counter);
 
            int offset = hash[hash.Length - 1] & 0xf;
 
            // Convert the 4 bytes into an integer, ignoring the sign.
            int binary =
                ((hash[offset] & 0x7f) << 24)
                | (hash[offset + 1] << 16)
                | (hash[offset + 2] << 8)
                | (hash[offset + 3]);
 
            int password = binary % (int)Math.Pow(10, digits);
            return password.ToString(new string(0, digits));
        }
 
        private long GetCurrentCounter()
        {
            return GetCurrentCounter(DateTime.UtcNow, _epoch, 30);
        }
 
        private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep)
        {
            return (long)(now - epoch).TotalSeconds / timeStep;
        }
 
        public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient)
        {
            return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance);
        }
 
        public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance)
        {
            var codes = GetCurrentPINs(accountSecretKey, timeTolerance);
            return codes.Any(c => c == twoFactorCodeFromClient);
        }
 
        public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance)
        {
            List<string> codes = new List<string>();
            long iterationCounter = GetCurrentCounter();
            int iterationOffset = 0;
 
            if (timeTolerance.TotalSeconds > 30)
            {
                iterationOffset = Convert.ToInt32(timeTolerance.TotalSeconds / 30.00);
            }
 
            long iterationStart = iterationCounter - iterationOffset;
            long iterationEnd = iterationCounter + iterationOffset;
 
            for (long counter = iterationStart; counter <= iterationEnd; counter++)
            {
                codes.Add(GeneratePINAtInterval(accountSecretKey, counter));
            }
 
            return codes.ToArray();
        }
 
        /// <summary>
        /// Writes a string into a bitmap
        /// </summary>
        /// <param name="qrCodeSetupImageUrl"></param>
        /// <returns></returns>
        public static Image GetQRCodeImage(string qrCodeSetupImageUrl)
        {
            // data:image/png;base64,
            qrCodeSetupImageUrl = qrCodeSetupImageUrl.Replace("data:image/png;base64,", "");
            Image img = null;
            byte[] buffer = Convert.FromBase64String(qrCodeSetupImageUrl);
            using (MemoryStream ms = new MemoryStream(buffer))
            {
                img = Image.FromStream(ms);
            }
            return img;
        }
    }
 
    public class Base32Encoding
    {
        /// <summary>
        /// Base32 encoded string to byte[]
        /// </summary>
        /// <param name="input">Base32 encoded string</param>
        /// <returns>byte[]</returns>
        public static byte[] ToBytes(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                throw new ArgumentNullException("input");
            }
 
            input = input.TrimEnd(=); //remove padding characters
            int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
            byte[] returnArray = new byte[byteCount];
 
            byte curByte = 0, bitsRemaining = 8;
            int mask = 0, arrayIndex = 0;
 
            foreach (char c in input)
            {
                int cValue = CharToValue(c);
 
                if (bitsRemaining > 5)
                {
                    mask = cValue << (bitsRemaining - 5);
                    curByte = (byte)(curByte | mask);
                    bitsRemaining -= 5;
                }
                else
                {
                    mask = cValue >> (5 - bitsRemaining);
                    curByte = (byte)(curByte | mask);
                    returnArray[arrayIndex++] = curByte;
                    curByte = (byte)(cValue << (3 + bitsRemaining));
                    bitsRemaining += 3;
                }
            }
 
            //if we didn‘t end with a full byte
            if (arrayIndex != byteCount)
            {
                returnArray[arrayIndex] = curByte;
            }
 
            return returnArray;
        }
 
        /// <summary>
        /// byte[] to Base32 string, if starting from an ordinary string use Encoding.UTF8.GetBytes() to convert it to a byte[]
        /// </summary>
        /// <param name="input">byte[] of data to be Base32 encoded</param>
        /// <returns>Base32 String</returns>
        public static string ToString(byte[] input)
        {
            if (input == null || input.Length == 0)
            {
                throw new ArgumentNullException("input");
            }
 
            int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
            char[] returnArray = new char[charCount];
 
            byte nextChar = 0, bitsRemaining = 5;
            int arrayIndex = 0;
 
            foreach (byte b in input)
            {
                nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
                returnArray[arrayIndex++] = ValueToChar(nextChar);
 
                if (bitsRemaining < 4)
                {
                    nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
                    returnArray[arrayIndex++] = ValueToChar(nextChar);
                    bitsRemaining += 5;
                }
 
                bitsRemaining -= 3;
                nextChar = (byte)((b << bitsRemaining) & 31);
            }
 
            //if we didn‘t end with a full char
            if (arrayIndex != charCount)
            {
                returnArray[arrayIndex++] = ValueToChar(nextChar);
                while (arrayIndex != charCount) returnArray[arrayIndex++] = =; //padding
            }
 
            return new string(returnArray);
        }
 
        private static int CharToValue(char c)
        {
            int value = (int)c;
 
            //65-90 == uppercase letters
            if (value < 91 && value > 64)
            {
                return value - 65;
            }
            //50-55 == numbers 2-7
            if (value < 56 && value > 49)
            {
                return value - 24;
            }
            //97-122 == lowercase letters
            if (value < 123 && value > 96)
            {
                return value - 97;
            }
 
            throw new ArgumentException("Character is not a Base32 character.", "c");
        }
 
        private static char ValueToChar(byte b)
        {
            if (b < 26)
            {
                return (char)(b + 65);
            }
 
            if (b < 32)
            {
                return (char)(b + 24);
            }
 
            throw new ArgumentException("Byte is not a value Base32 value.", "b");
        }
    }

使用和验证:默认的index.cshtml

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
    string key = "123456";
    string issuer = "Google";
    string user = "Gavin@gamil.com";
 
    // 生成 SetupCode
    var code = new GoogleAuthenticator().GenerateSetupCode(issuer, user, key, 5);
}
 
<div class="text-center">
    <h1 class="display-4">GoogleAuthenticator</h1><br />
    <p>Account:@code.Account</p><br/>
    <p>Key:@code.ManualEntryKey</p><br />
    <img  src="@code.QrCodeSetupImageUrl"/>
</div>

新建token.cshtml来验证token:

@page
@model GoogleAuthenticatorTest.Pages.tokenModel
@{
    ViewData["Title"] = "Home page";
 
    string key = "123456";
    string token = HttpContext.Request.Query["token"];
    string result = "token验证失败";
    if (!string.IsNullOrEmpty(token)){
        GoogleAuthenticator gat = new GoogleAuthenticator();
        if (gat.ValidateTwoFactorPIN(key, token)) {
            result = "token验证成功";
        }
    }
 
}
 
 
 
<div class="text-center">
    <p>@result</p>
 
</div>

运行效果(环境: win10 + asp.netcore5.0 + vscode):

技术图片

三、GO 直接实现

我在网上找了一个 https://github.com/928799934/googleAuthenticator.git直接拿来用了以西, 可以的,首先二维码 otpauth://totp/gitlab.com:410534805@qq.com?secret=LC42VPXL3VUMBCAN&issuer=gitlab.com 我们可以用草料来生成 , 然后用手机来扫码,然后在调用API来验证:

package main
 
import (
    "fmt"
    "gotest/googleAuthenticator"
)
 
func createSecret(ga *googleAuthenticator.GAuth) string {
    secret, err := ga.CreateSecret(16)
    if err != nil {
        return ""
    }
    return secret
}
 
func getCode(ga *googleAuthenticator.GAuth, secret string) string {
    code, err := ga.GetCode(secret)
    if err != nil {
        return "*"
    }
    return code
}
 
func verifyCode(ga *googleAuthenticator.GAuth, secret, code string) bool {
    // 1:30sec
    ret, err := ga.VerifyCode(secret, code, 1)
    if err != nil {
        return false
    }
    return ret
}
 
func main() {
    /*
        if len(os.Args) != 2 {
            return
        }
    */
    // 用草料二维码 https://cli.im/ 生成 以下地址:
    //otpauth://totp/gitlab.com:410534805@qq.com?secret=LC42VPXL3VUMBCAN&issuer=gitlab.com
    secret := "LC42VPXL3VUMBCAN"
    //secret := os.Args[1]
    //secret := "IU7B5Q3VBL55Q645"
    ga := googleAuthenticator.NewGAuth()
    //code := getCode(ga, secret)
    code := "027093"
    ret := verifyCode(ga, secret, code)
    fmt.Println(ret)
 
}

最后运行通过

下载地址:

https://github.com/dz45693/GoogleAuthenticatorTest.git

谷歌身份验证 asp.net core和go的实现

标签:div   装包   counter   col   client   rem   rev   line   攻击   

原文地址:https://www.cnblogs.com/majiang/p/14342662.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!