博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Google Authenticator(谷歌身份验证器)C#版
阅读量:6948 次
发布时间:2019-06-27

本文共 8949 字,大约阅读时间需要 29 分钟。

摘要: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计数器进行算法计算

认证器安装

手机需要安装认证器:

  • Android版:
  • IOS版:

效果图

案例

控制台

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         ///  10         /// 初始化验证码生成规则 11         ///  12         /// 秘钥(手机使用Base32码) 13         /// 验证码间隔多久刷新一次(默认30秒和google同步) 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         ///  22         /// 间隔时间 23         ///  24         private long DURATION_TIME { get; set; } 25  26         ///  27         /// 迭代次数 28         ///  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         ///  38         /// 秘钥 39         ///  40         private string SERECT_KEY { get; set; } 41  42         ///  43         /// 手机端输入的秘钥 44         ///  45         private string SERECT_KEY_MOBILE { get; set; } 46  47         ///  48         /// 到期秒数 49         ///  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         ///  59         /// 获取手机端秘钥 60         ///  61         /// 
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 /// 70 /// 生成认证码 71 /// 72 ///
返回验证码
73 public string GenerateCode() 74 { 75 return GenerateHashedCode(SERECT_KEY, COUNTER); 76 } 77 78 /// 79 /// 按照次数生成哈希编码 80 /// 81 /// 秘钥 82 /// 迭代次数 83 /// 生成位数 84 ///
返回验证码
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 digits107 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:

gitee:

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

 

转载于:https://www.cnblogs.com/easyauthor/p/11054869.html

你可能感兴趣的文章
关于预编绎网站的问题[已预编译此应用程序的错误]
查看>>
BZOJ 4869: [Shoi2017]相逢是问候
查看>>
Solr数据备份
查看>>
系统管理模块_部门管理_实现基本的增删改查功能
查看>>
web项目部署到阿里云服务器步骤
查看>>
ABP理论学习之依赖注入
查看>>
php读取文件的各种方法
查看>>
re模块 正则表达式
查看>>
06-标准文档流,块级元素和行内元素,浮动,margin的用法
查看>>
tomcat的默认路径ROOT怎么改变,我想设置tomcat的默认路径,怎么设置?
查看>>
sqlite使用手册(转)
查看>>
学习:深度和广度之谈
查看>>
leetcode495
查看>>
用分解的方式学算法002——插入排序
查看>>
剑指Offer 16 数值的整数次方
查看>>
Intent 调用系统中经常用到的组件
查看>>
011PHP基础知识——运算符(四)
查看>>
SpringBoot集成WebSocket【基于STOMP协议】进行点对点[一对一]和广播[一对多]实时推送...
查看>>
20060308: WOW角色不见了
查看>>
iOS 横屏模态进入下一级界面, 竖屏退出
查看>>