• Google Authenticator(谷歌身份验证器)C#版


    摘要:Google Authenticator(谷歌身份验证器),是谷歌公司推出的一款动态令牌工具,解决账户使用时遭到的一些不安全的操作进行的“二次验证”,认证器基于RFC文档中的HOTP/TOTP算法实现 ,是一种从共享秘钥和时间或次数一次性令牌的算法。在工作中可以通过认证器方式对账户有更好的保护,但是在查阅一些资料发现适合我这样的小白文章真的很少,针对于C#的文章就更加少了,本文主要是对C#如何使用Google Authenticator(谷歌身份验证器)进行探讨,有不足之处还请见谅。

    Google Authenticator(谷歌身份验证器)

    什么是认证器?怎么对接?

    Google Authenticator(谷歌身份验证器)是微软推出的一个动态密令工具,它有两种密令模式。分别是“TOTP 基于时间”、“HOTP 基于计数器”,通过手机上 简单的设置就可以设定自己独一的动态密令, 那么我们怎么将我们的程序和认证器进行对接呢?其实谷歌认证器并不是需要我们对接这个工具的API而是通过算法来决定,谷歌使用使用HMAC算法生成密令,通过基于次数或者基于时间两个模板进行计算,因此在程序中只需要使用相同的算法即可与之匹配。

    TOTP 基于时间

    • HMAC算法使用固定为HmacSHA1
    • 更新时长固定为30秒
    • APP端输入数据维度只有两个:账户名称(自己随意填写方便自己查看)和base32格式的key

    HOTP 基于计数器

    基于计数器模式是根据一个共享秘钥K和一个C计数器进行算法计算

    认证器安装

    手机需要安装认证器:

    效果图

    案例

    控制台

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 
     6 namespace GoogleAuthenticator
     7 {
     8     class Program
     9     {
    10         static void Main(string[] args)
    11         {
    12             long duration = 30;
    13             string key = "xeon997@foxmail.com";
    14             GoogleAuthenticator authenticator = new GoogleAuthenticator(duration, key);
    15             var mobileKey = authenticator.GetMobilePhoneKey();
    16 
    17             while (true)
    18             {
    19 
    20                 Console.WriteLine("手机端秘钥为:" + mobileKey);
    21 
    22                 var code = authenticator.GenerateCode();
    23                 Console.WriteLine("动态验证码为:" + code);
    24 
    25                 Console.WriteLine("刷新倒计时:" + authenticator.EXPIRE_SECONDS);
    26 
    27                 System.Threading.Thread.Sleep(1000);
    28                 Console.Clear();
    29             }
    30         }
    31     }
    32 }
    View Code

    认证器类

      1 using GoogleAuthorization;
      2 using System;
      3 using System.Security.Cryptography;
      4 using System.Text;
      5 namespace GoogleAuthenticator
      6 {
      7     public class GoogleAuthenticator
      8     {
      9         /// <summary>
     10         /// 初始化验证码生成规则
     11         /// </summary>
     12         /// <param name="key">秘钥(手机使用Base32码)</param>
     13         /// <param name="duration">验证码间隔多久刷新一次(默认30秒和google同步)</param>
     14         public GoogleAuthenticator(long duration = 30, string key = "xeon997@foxmail.com")
     15         {
     16             this.SERECT_KEY = key;
     17             this.SERECT_KEY_MOBILE = Base32.ToString(Encoding.UTF8.GetBytes(key));
     18             this.DURATION_TIME = duration;
     19         }
     20 
     21         /// <summary>
     22         /// 间隔时间
     23         /// </summary>
     24         private long DURATION_TIME { get; set; }
     25 
     26         /// <summary>
     27         /// 迭代次数
     28         /// </summary>
     29         private long COUNTER
     30         {
     31             get
     32             {
     33                 return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds / DURATION_TIME;
     34             }
     35         }
     36 
     37         /// <summary>
     38         /// 秘钥
     39         /// </summary>
     40         private string SERECT_KEY { get; set; }
     41 
     42         /// <summary>
     43         /// 手机端输入的秘钥
     44         /// </summary>
     45         private string SERECT_KEY_MOBILE { get; set; }
     46 
     47         /// <summary>
     48         /// 到期秒数
     49         /// </summary>
     50         public long EXPIRE_SECONDS
     51         {
     52             get
     53             {
     54                 return (DURATION_TIME - (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds % DURATION_TIME);
     55             }
     56         }
     57 
     58         /// <summary>
     59         /// 获取手机端秘钥
     60         /// </summary>
     61         /// <returns></returns>
     62         public string GetMobilePhoneKey()
     63         {
     64             if (SERECT_KEY_MOBILE == null)
     65                 throw new ArgumentNullException("SERECT_KEY_MOBILE");
     66             return SERECT_KEY_MOBILE;
     67         }
     68 
     69         /// <summary>
     70         /// 生成认证码
     71         /// </summary>
     72         /// <returns>返回验证码</returns>
     73         public string GenerateCode()
     74         {
     75             return GenerateHashedCode(SERECT_KEY, COUNTER);
     76         }
     77 
     78         /// <summary>
     79         /// 按照次数生成哈希编码
     80         /// </summary>
     81         /// <param name="secret">秘钥</param>
     82         /// <param name="iterationNumber">迭代次数</param>
     83         /// <param name="digits">生成位数</param>
     84         /// <returns>返回验证码</returns>
     85         private string GenerateHashedCode(string secret, long iterationNumber, int digits = 6)
     86         {
     87             byte[] counter = BitConverter.GetBytes(iterationNumber);
     88 
     89             if (BitConverter.IsLittleEndian)
     90                 Array.Reverse(counter);
     91 
     92             byte[] key = Encoding.ASCII.GetBytes(secret);
     93 
     94             HMACSHA1 hmac = new HMACSHA1(key, true);
     95 
     96             byte[] hash = hmac.ComputeHash(counter);
     97 
     98             int offset = hash[hash.Length - 1] & 0xf;
     99 
    100             int binary =
    101                 ((hash[offset] & 0x7f) << 24)
    102                 | ((hash[offset + 1] & 0xff) << 16)
    103                 | ((hash[offset + 2] & 0xff) << 8)
    104                 | (hash[offset + 3] & 0xff);
    105 
    106             int password = binary % (int)Math.Pow(10, digits); // 6 digits
    107 
    108             return password.ToString(new string('0', digits));
    109         }
    110     }
    111 }
    View Code

    Base32转码类

      1 using System;
      2 namespace GoogleAuthorization
      3 {
      4     public static class Base32
      5     {
      6         public static byte[] ToBytes(string input)
      7         {
      8             if (string.IsNullOrEmpty(input))
      9             {
     10                 throw new ArgumentNullException("input");
     11             }
     12 
     13             input = input.TrimEnd('='); 
     14             int byteCount = input.Length * 5 / 8; 
     15             byte[] returnArray = new byte[byteCount];
     16 
     17             byte curByte = 0, bitsRemaining = 8;
     18             int mask = 0, arrayIndex = 0;
     19 
     20             foreach (char c in input)
     21             {
     22                 int cValue = CharToValue(c);
     23 
     24                 if (bitsRemaining > 5)
     25                 {
     26                     mask = cValue << (bitsRemaining - 5);
     27                     curByte = (byte)(curByte | mask);
     28                     bitsRemaining -= 5;
     29                 }
     30                 else
     31                 {
     32                     mask = cValue >> (5 - bitsRemaining);
     33                     curByte = (byte)(curByte | mask);
     34                     returnArray[arrayIndex++] = curByte;
     35                     curByte = (byte)(cValue << (3 + bitsRemaining));
     36                     bitsRemaining += 3;
     37                 }
     38             }
     39 
     40             if (arrayIndex != byteCount)
     41             {
     42                 returnArray[arrayIndex] = curByte;
     43             }
     44 
     45             return returnArray;
     46         }
     47 
     48         public static string ToString(byte[] input)
     49         {
     50             if (input == null || input.Length == 0)
     51             {
     52                 throw new ArgumentNullException("input");
     53             }
     54 
     55             int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
     56             char[] returnArray = new char[charCount];
     57 
     58             byte nextChar = 0, bitsRemaining = 5;
     59             int arrayIndex = 0;
     60 
     61             foreach (byte b in input)
     62             {
     63                 nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
     64                 returnArray[arrayIndex++] = ValueToChar(nextChar);
     65 
     66                 if (bitsRemaining < 4)
     67                 {
     68                     nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
     69                     returnArray[arrayIndex++] = ValueToChar(nextChar);
     70                     bitsRemaining += 5;
     71                 }
     72 
     73                 bitsRemaining -= 3;
     74                 nextChar = (byte)((b << bitsRemaining) & 31);
     75             }
     76 
     77             if (arrayIndex != charCount)
     78             {
     79                 returnArray[arrayIndex++] = ValueToChar(nextChar);
     80                 while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; 
     81             }
     82 
     83             return new string(returnArray);
     84         }
     85 
     86         private static int CharToValue(char c)
     87         {
     88             var value = (int)c;
     89 
     90             if (value < 91 && value > 64)
     91             {
     92                 return value - 65;
     93             }
     94             if (value < 56 && value > 49)
     95             {
     96                 return value - 24;
     97             }
     98             if (value < 123 && value > 96)
     99             {
    100                 return value - 97;
    101             }
    102 
    103             throw new ArgumentException("Character is not a Base32 character.", "c");
    104         }
    105 
    106         private static char ValueToChar(byte b)
    107         {
    108             if (b < 26)
    109             {
    110                 return (char)(b + 65);
    111             }
    112 
    113             if (b < 32)
    114             {
    115                 return (char)(b + 24);
    116             }
    117 
    118             throw new ArgumentException("Byte is not a value Base32 value.", "b");
    119         }
    120     }
    121 }
    View Code

    总结

    需要注意的坑

    移动端下载的认证器的秘钥key是通过base32转码得到的,而程序端是直接输入源码。

    如原秘钥为xeon997@foxmail.com生成的base32码PBSW63RZHE3UAZTPPBWWC2LMFZRW63I=才是移动端需要输入的秘钥。

    在网上找了很多资料没有发现关于C#的案例,所以在此记录一下自己遇到的坑,让更多的人能够跳过这个坑。

    案例地址:

    git:https://github.com/CN-Yi/GoogleAuthenticator

    gitee:https://gitee.com/hsyi/GoogleAuthenticator

    在此感谢提供学习资料的大神们,如果有错误的地方欢迎留言。

     
  • 相关阅读:
    MySQL information_schema
    Sqlmap入门
    MySQL UNION
    Order by 1
    yum安装软件时,提示No package netstat available.的解决方法
    Centos7查看端口占用
    查看Centos版本
    Linux非交互方式设置密码
    Hive中的用户自定义函数
    Dbeaver连接Hive和Mysql的配置
  • 原文地址:https://www.cnblogs.com/hsyi/p/11054869.html
Copyright © 2020-2023  润新知