c# 使用谷歌身份验证GoogleAuthenticator的示例

 更新时间:2021年01月29日 08:29:06   作者:心如大海  
这篇文章主要介绍了c# 使用谷歌身份验证GoogleAuthenticator的示例,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下

此功能相当于给系统加了个令牌,只有输入对的一组数字才可以验证成功。类似于QQ令牌一样。

一丶创建最核心的一个类GoogleAuthenticator

此类包含了生成密钥,验证,将绑定密钥转为二维码。

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;
      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();
    }
  }

其中GenerateSetupCode 这个方法是用于把绑定的密钥直接转成二维码图片,然后再转成base64图片 输出再页面上,这样在APP上直接用扫一扫即可绑定。

二丶由于生成的密钥不可以直接使用,需要进行Base32进行编码。下面是Base32Encoding类

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");
    }
  }

三丶主程序里面直接调用方法

 private SetupCode Google(string key, string Guids)
 {
   GoogleAuthenticator gat = new GoogleAuthenticator();
   return gat.GenerateSetupCode("Supported Giving", key, Guids, 5);
 }

//key系统的账号,Guid是进行加密的字符串,要求唯一,不然密钥会重复,所以这里使用Guid.   2为二维码的大小约120x120px。

SetupCode结果类为

public class SetupCode
  {
    public string Account { get; internal set; }
    public string AccountSecretKey { get; internal set; }
    public string ManualEntryKey { get; internal set; }
    /// <summary>
    /// Base64-encoded PNG image
    /// </summary>
    public string QrCodeSetupImageUrl { get; internal set; }
}

ManualEntryKey 是手机绑定的密钥。如果想手动输入密钥绑定就使用此字符串。
QrCodeSetupImageUrl 是将密钥转成的二维码图片

下载这个APP

进入APP后直接绑定,就会出现一下界面,即为绑定成功,然后我们就可以使用此令牌验证了。

验证方法

//Guids 之前生成密钥的字符,此时当做唯一键来查询,CheckCode为手机上动态的6位验证吗。校验成功会返回true

GoogleAuthenticator gat = new GoogleAuthenticator();
var result = gat.ValidateTwoFactorPIN(parameters["Guids"].ToString(), parameters["CheckCode"].ToString());
if (result)
{
return "True";
}
else
{
return "False";
}

这样功能就完成了。

以上就是c# 使用谷歌身份验证GoogleAuthenticator的示例的详细内容,更多关于c# 使用谷歌身份验证GoogleAuthenticator的资料请关注脚本之家其它相关文章!

相关文章

  • WinForm调用百度地图接口用法示例

    WinForm调用百度地图接口用法示例

    这篇文章主要介绍了WinForm调用百度地图接口用法,结合具体实例形式简单分析了WinForm WebBrower控件与前端百度接口交互的相关操作技巧,需要的朋友可以参考下
    2017-06-06
  • 10分钟学会Visual Studio将自己创建的类库打包到NuGet进行引用(net,net core,C#)

    10分钟学会Visual Studio将自己创建的类库打包到NuGet进行引用(net,net core,C#)

    这篇文章主要介绍了10分钟学会Visual Studio将自己创建的类库打包到NuGet进行引用(net,net core,C#),本文给大家介绍的非常详细对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • C# 正则表达式进阶

    C# 正则表达式进阶

    这篇文章主要介绍了C# 正则表达式进阶,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • C#开发Winform实现学生管理系统

    C#开发Winform实现学生管理系统

    这篇文章介绍了C#开发Winform实现学生管理系统的项目案例,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • C#实现文件与字符串互转的方法详解

    C#实现文件与字符串互转的方法详解

    这篇文章主要为大家详细介绍了如何利用C#实现文件与字符串互转效果,文中的示例代码讲解详细,对我们学习C#有一定帮助,需要的可以参考一下
    2022-08-08
  • C#简单实现IOC容器的示例代码

    C#简单实现IOC容器的示例代码

    IoC 的原理是通过将对象的创建和依赖关系的管理交给外部容器来实现,从而降低了代码的耦合度,提高了代码的可维护性和可测试性,下面我们就来看看如何通过C#实现一个IOC容器吧
    2024-02-02
  • C#对XmlHelper帮助类操作Xml文档的通用方法汇总

    C#对XmlHelper帮助类操作Xml文档的通用方法汇总

    该篇文章主要总结的是自己平时工作中使用频率比较高的Xml文档操作的一些常用方法和收集网上写的比较好的一些通用Xml文档操作的方法,对C# XmlHelper帮助类操作Xml文档相关知识感兴趣的朋友一起看看吧
    2022-03-03
  • c#读取excel方法实例分析

    c#读取excel方法实例分析

    这篇文章主要介绍了c#读取excel方法,实例分析了C#读取excel文件的原理与相关技巧,需要的朋友可以参考下
    2015-06-06
  • Unity3D实现甜品消消乐游戏

    Unity3D实现甜品消消乐游戏

    这篇文章主要介绍了通过C# Unity3D绘制一个甜品消消乐游戏,文中的示例代码讲解详细,对我们学习或工作有一定的帮助,感兴趣的小伙伴可以学习一下
    2021-12-12
  • C# 设计模式系列教程-桥接模式

    C# 设计模式系列教程-桥接模式

    桥接模式降低了沿着两个或多个维度扩展时的复杂度,防止类的过度膨胀,解除了两个或多个维度之间的耦合,使它们沿着各自方向变化而不互相影响。
    2016-06-06

最新评论