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

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

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

  1 public class GoogleAuthenticator
2 {
3 private readonly static DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
4 private TimeSpan DefaultClockDriftTolerance { get; set; }
5
6 public GoogleAuthenticator()
7 {
8 DefaultClockDriftTolerance = TimeSpan.FromMinutes(5);
9 }
10
11 /// <summary>
12 /// Generate a setup code for a Google Authenticator user to scan
13 /// </summary>
14 /// <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>
15 /// <param name="accountTitleNoSpaces">Account Title (no spaces)</param>
16 /// <param name="accountSecretKey">Account Secret Key</param>
17 /// <param name="QRPixelsPerModule">Number of pixels per QR Module (2 pixels give ~ 100x100px QRCode)</param>
18 /// <returns>SetupCode object</returns>
19 public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, int QRPixelsPerModule)
20 {
21 byte[] key = Encoding.UTF8.GetBytes(accountSecretKey);
22 return GenerateSetupCode(issuer, accountTitleNoSpaces, key, QRPixelsPerModule);
23 }
24
25 /// <summary>
26 /// Generate a setup code for a Google Authenticator user to scan
27 /// </summary>
28 /// <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>
29 /// <param name="accountTitleNoSpaces">Account Title (no spaces)</param>
30 /// <param name="accountSecretKey">Account Secret Key as byte[]</param>
31 /// <param name="QRPixelsPerModule">Number of pixels per QR Module (2 = ~120x120px QRCode)</param>
32 /// <returns>SetupCode object</returns>
33 public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, byte[] accountSecretKey, int QRPixelsPerModule)
34 {
35 if (accountTitleNoSpaces == null) { throw new NullReferenceException("Account Title is null"); }
36 accountTitleNoSpaces = RemoveWhitespace(accountTitleNoSpaces);
37 string encodedSecretKey = Base32Encoding.ToString(accountSecretKey);
38 string provisionUrl = null;
39 provisionUrl = String.Format("otpauth://totp/{2}:{0}?secret={1}&issuer={2}", accountTitleNoSpaces, encodedSecretKey.Replace("=",""), UrlEncode(issuer));
40
41
42
43 using (QRCodeGenerator qrGenerator = new QRCodeGenerator())
44 using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(provisionUrl, QRCodeGenerator.ECCLevel.M))
45 using (QRCode qrCode = new QRCode(qrCodeData))
46 using (Bitmap qrCodeImage = qrCode.GetGraphic(QRPixelsPerModule))
47 using (MemoryStream ms = new MemoryStream())
48 {
49 qrCodeImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
50
51 return new SetupCode(accountTitleNoSpaces, encodedSecretKey, String.Format("data:image/png;base64,{0}", Convert.ToBase64String(ms.ToArray())));
52 }
53
54 }
55
56 private static string RemoveWhitespace(string str)
57 {
58 return new string(str.Where(c => !Char.IsWhiteSpace(c)).ToArray());
59 }
60
61 private string UrlEncode(string value)
62 {
63 StringBuilder result = new StringBuilder();
64 string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
65
66 foreach (char symbol in value)
67 {
68 if (validChars.IndexOf(symbol) != -1)
69 {
70 result.Append(symbol);
71 }
72 else
73 {
74 result.Append('%' + String.Format("{0:X2}", (int)symbol));
75 }
76 }
77
78 return result.ToString().Replace(" ", "%20");
79 }
80
81 public string GeneratePINAtInterval(string accountSecretKey, long counter, int digits = 6)
82 {
83 return GenerateHashedCode(accountSecretKey, counter, digits);
84 }
85
86 internal string GenerateHashedCode(string secret, long iterationNumber, int digits = 6)
87 {
88 byte[] key = Encoding.UTF8.GetBytes(secret);
89 return GenerateHashedCode(key, iterationNumber, digits);
90 }
91
92 internal string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6)
93 {
94 byte[] counter = BitConverter.GetBytes(iterationNumber);
95
96 if (BitConverter.IsLittleEndian)
97 {
98 Array.Reverse(counter);
99 }
100
101 HMACSHA1 hmac = new HMACSHA1(key);
102
103 byte[] hash = hmac.ComputeHash(counter);
104
105 int offset = hash[hash.Length - 1] & 0xf;
106
107 // Convert the 4 bytes into an integer, ignoring the sign.
108 int binary =
109 ((hash[offset] & 0x7f) << 24)
110 | (hash[offset + 1] << 16)
111 | (hash[offset + 2] << 8)
112 | (hash[offset + 3]);
113
114 int password = binary % (int)Math.Pow(10, digits);
115 return password.ToString(new string('0', digits));
116 }
117
118 private long GetCurrentCounter()
119 {
120 return GetCurrentCounter(DateTime.UtcNow, _epoch, 30);
121 }
122
123 private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep)
124 {
125 return (long)(now - epoch).TotalSeconds / timeStep;
126 }
127
128 public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient)
129 {
130 return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance);
131 }
132
133 public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance)
134 {
135 var codes = GetCurrentPINs(accountSecretKey, timeTolerance);
136 return codes.Any(c => c == twoFactorCodeFromClient);
137 }
138
139 public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance)
140 {
141 List<string> codes = new List<string>();
142 long iterationCounter = GetCurrentCounter();
143 int iterationOffset = 0;
144
145 if (timeTolerance.TotalSeconds > 30)
146 {
147 iterationOffset = Convert.ToInt32(timeTolerance.TotalSeconds / 30.00);
148 }
149
150 long iterationStart = iterationCounter - iterationOffset;
151 long iterationEnd = iterationCounter + iterationOffset;
152
153 for (long counter = iterationStart; counter <= iterationEnd; counter++)
154 {
155 codes.Add(GeneratePINAtInterval(accountSecretKey, counter));
156 }
157
158 return codes.ToArray();
159 }
160 }

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

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

  1     public class Base32Encoding
2 {
3 /// <summary>
4 /// Base32 encoded string to byte[]
5 /// </summary>
6 /// <param name="input">Base32 encoded string</param>
7 /// <returns>byte[]</returns>
8 public static byte[] ToBytes(string input)
9 {
10 if (string.IsNullOrEmpty(input))
11 {
12 throw new ArgumentNullException("input");
13 }
14
15 input = input.TrimEnd('='); //remove padding characters
16 int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
17 byte[] returnArray = new byte[byteCount];
18
19 byte curByte = 0, bitsRemaining = 8;
20 int mask = 0, arrayIndex = 0;
21
22 foreach (char c in input)
23 {
24 int cValue = CharToValue(c);
25
26 if (bitsRemaining > 5)
27 {
28 mask = cValue << (bitsRemaining - 5);
29 curByte = (byte)(curByte | mask);
30 bitsRemaining -= 5;
31 }
32 else
33 {
34 mask = cValue >> (5 - bitsRemaining);
35 curByte = (byte)(curByte | mask);
36 returnArray[arrayIndex++] = curByte;
37 curByte = (byte)(cValue << (3 + bitsRemaining));
38 bitsRemaining += 3;
39 }
40 }
41
42 //if we didn't end with a full byte
43 if (arrayIndex != byteCount)
44 {
45 returnArray[arrayIndex] = curByte;
46 }
47
48 return returnArray;
49 }
50
51 /// <summary>
52 /// byte[] to Base32 string, if starting from an ordinary string use Encoding.UTF8.GetBytes() to convert it to a byte[]
53 /// </summary>
54 /// <param name="input">byte[] of data to be Base32 encoded</param>
55 /// <returns>Base32 String</returns>
56 public static string ToString(byte[] input)
57 {
58 if (input == null || input.Length == 0)
59 {
60 throw new ArgumentNullException("input");
61 }
62
63 int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
64 char[] returnArray = new char[charCount];
65
66 byte nextChar = 0, bitsRemaining = 5;
67 int arrayIndex = 0;
68
69 foreach (byte b in input)
70 {
71 nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
72 returnArray[arrayIndex++] = ValueToChar(nextChar);
73
74 if (bitsRemaining < 4)
75 {
76 nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
77 returnArray[arrayIndex++] = ValueToChar(nextChar);
78 bitsRemaining += 5;
79 }
80
81 bitsRemaining -= 3;
82 nextChar = (byte)((b << bitsRemaining) & 31);
83 }
84
85 //if we didn't end with a full char
86 if (arrayIndex != charCount)
87 {
88 returnArray[arrayIndex++] = ValueToChar(nextChar);
89 while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
90 }
91
92 return new string(returnArray);
93 }
94
95 private static int CharToValue(char c)
96 {
97 int value = (int)c;
98
99 //65-90 == uppercase letters
100 if (value < 91 && value > 64)
101 {
102 return value - 65;
103 }
104 //50-55 == numbers 2-7
105 if (value < 56 && value > 49)
106 {
107 return value - 24;
108 }
109 //97-122 == lowercase letters
110 if (value < 123 && value > 96)
111 {
112 return value - 97;
113 }
114
115 throw new ArgumentException("Character is not a Base32 character.", "c");
116 }
117
118 private static char ValueToChar(byte b)
119 {
120 if (b < 26)
121 {
122 return (char)(b + 65);
123 }
124
125 if (b < 32)
126 {
127 return (char)(b + 24);
128 }
129
130 throw new ArgumentException("Byte is not a value Base32 value.", "b");
131 }
132 }

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

1  private SetupCode Google(string key, string Guids)
2 {
3 GoogleAuthenticator gat = new GoogleAuthenticator();
4 return gat.GenerateSetupCode("Supported Giving", key, Guids, 5);
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的更多相关文章

  1. Google Authenticator(谷歌身份验证器)

    <!DOCTYPE html>Google Authenticator(谷歌身份验证器) ] Google Authenticator(谷歌身份验证器) Google Authentica ...

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

    摘要:Google Authenticator(谷歌身份验证器),是谷歌公司推出的一款动态令牌工具,解决账户使用时遭到的一些不安全的操作进行的"二次验证",认证器基于RFC文档中的 ...

  3. Google authenticator 谷歌身份验证,实现动态口令

    Google authenticator 谷歌身份验证,实现动态口令 google authenticator php 服务端 使用PHP类 require_once '../PHPGangsta/G ...

  4. OKex平台如何使用谷歌身份验证?

    打开OK交易所官网,找到谷歌身份验证器的开启界面 登陆后点击右上角头像-账户和安全 然后[安全设置]里出现“谷歌验证”的位置,点击开启按钮,到了二维码和密钥显示的界面 我们不使用谷歌身份验证器,因为需 ...

  5. 七牛云如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?

    一般情况下,点账户名——账户设置——安全设置,即可开通两步验证 具体步骤见链接  七牛云如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?   二次验证码小程序(官网)对比谷歌身份验证器APP ...

  6. humlbe bundle如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?

    一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 humlbe bundle如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的 ...

  7. R星游戏如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?

    一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 R星游戏如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载 ...

  8. WBF交易所如何使用二次验证码/谷歌身份验证器

    一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接  WBF交易所如何使用二次验证码/谷歌身份验证器 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载app 2.验证码 ...

  9. 关于虎信如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?

    一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 虎信如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载ap ...

随机推荐

  1. Python的环境是如何安装的,我来教你

    01 初见Python Python编程语言是荷兰人Guido van Rossum在1990年代开发出来的. Gudio拥数学和计算机双硕士学位,但他更喜欢计算机.当时Gudio觉得现有的编程语言无 ...

  2. NET Core 使用EF Core的Code First迁移和DBFirst

    DBFirst (1)Microsoft.EntityFrameworkCore (2)Microsoft.EntityFrameworkCore.Design (3)Microsoft.Entity ...

  3. 记一次Ajax请求MVC调优

    问题描述 ASP.NETMVC项目,编辑页面赋值操作,ajax请求很慢. 对同一个控制器有2个请求 一个为获取该表的树形结构数据(绑定下拉框) 一个为获取某实体的单条记录(编辑操作) 排查过程 数据库 ...

  4. VS中RDLC提示类型不一致

    错误"基类包括字段"XXXXXXX",但其类型(Microsoft.Reporting.WebForms.ReportViewer)与控件(Microsoft.Repor ...

  5. [LeetCode]86. Partition List分离链表

    /* 这个题是medium的意思应该是用双指针的方法做,如果使用下边的新建链表的方法,就是easy的题目了 双指针会用到很多链表的相连操作 */ public ListNode partition(L ...

  6. [leetcode]516. Longest Palindromic Subsequence最大回文子序列

    Given a string s, find the longest palindromic subsequence's length in s. You may assume that the ma ...

  7. 使用mono-repo实现跨项目组件共享

    本文会分享一个我在实际工作中遇到的案例,从最开始的需求分析到项目搭建,以及最后落地的架构的整个过程.最终实现的效果是使用mono-repo实现了跨项目的组件共享.在本文中你可以看到: 从接到需求到深入 ...

  8. CSS系列 (05):浮动详解

    浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止.由于浮动框不在文档的普通流中,所以文档的普通流中的块框表现得就像浮动框不存在一样. -- W3C 文字环绕 float可以 ...

  9. 前端学习总结之——HTML

    近期在找工作参加面试,想总结一下学过的东西,也会持续更新遇到的新问题.盲点. 什么是HTML? 超文本标记语言(英语:HyperText Markup Language,简称:HTML),由尖括号包围 ...

  10. loj #6179. Pyh 的求和 莫比乌斯反演

    题目描述 传送门 求 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^m \varphi(ij)(mod\ 998244353)\) \(T\) 组询问 \(1 \leq ...