c#使用谷歌身份验证GoogleAuthenticator
此功能相当于给系统加了个令牌,只有输入对的一组数字才可以验证成功。类似于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的更多相关文章
- Google Authenticator(谷歌身份验证器)
<!DOCTYPE html>Google Authenticator(谷歌身份验证器) ] Google Authenticator(谷歌身份验证器) Google Authentica ...
- Google Authenticator(谷歌身份验证器)C#版
摘要:Google Authenticator(谷歌身份验证器),是谷歌公司推出的一款动态令牌工具,解决账户使用时遭到的一些不安全的操作进行的"二次验证",认证器基于RFC文档中的 ...
- Google authenticator 谷歌身份验证,实现动态口令
Google authenticator 谷歌身份验证,实现动态口令 google authenticator php 服务端 使用PHP类 require_once '../PHPGangsta/G ...
- OKex平台如何使用谷歌身份验证?
打开OK交易所官网,找到谷歌身份验证器的开启界面 登陆后点击右上角头像-账户和安全 然后[安全设置]里出现“谷歌验证”的位置,点击开启按钮,到了二维码和密钥显示的界面 我们不使用谷歌身份验证器,因为需 ...
- 七牛云如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?
一般情况下,点账户名——账户设置——安全设置,即可开通两步验证 具体步骤见链接 七牛云如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器? 二次验证码小程序(官网)对比谷歌身份验证器APP ...
- humlbe bundle如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 humlbe bundle如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的 ...
- R星游戏如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 R星游戏如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载 ...
- WBF交易所如何使用二次验证码/谷歌身份验证器
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 WBF交易所如何使用二次验证码/谷歌身份验证器 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载app 2.验证码 ...
- 关于虎信如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 虎信如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载ap ...
随机推荐
- vs2010新特性
下面列出了一些新的功能:1.代码编辑器新的代码编辑器使代码更易于阅读.可以通过按 CTRL 并滚动鼠标轮放大文本.此外,单击 Visual C# 或 Visual Basic 中的符号时该符号的所有实 ...
- (九)rmdir和rm -r删除目录命令
一.命令描述与格式 rmdir用于删除空目录 命令格式 :rmdir [选项] 目录名 选项: --ignore-fail-on-non-empty :忽略任何因目录仍有数据而造成的错误 ...
- 自定义 demo 集合
各种写着玩的自定义控件demo 有时网上看到一些比较有意思的开源项目,有时间的话就会自己也撸一个出来,但是一般只关注实现样式.动画等,不会太去细致完整地完成,俗称占个坑~ 持续更新中... githu ...
- java中根据后端返回的数据加载table列表
<%//引入 js @ page language="java" pageEncoding="UTF-8"%> <!DOCTYPE HTML& ...
- 白嫖JetBrains正版全家桶!
使用自己的开源项目,是可以白嫖JetBrains正版全家桶的! 前言 之前在学Go的时候,想着要用什么编辑器,网上的大佬都讲,想省事直接用Goland,用VsCode配置会存在一些未知的使用体验问题, ...
- SM4
整体结构 T变换 SM4解密的合理性证明 秘钥扩展
- Java学习日报10.2
1 package random; 2 import java.util.*; 3 import java.math.*; 4 public class Com { 5 6 public static ...
- Pytest测试框架(一):pytest安装及用例执行
PyTest是基于Python的开源测试框架,语法简单易用,有大量的插件,功能非常多.自动检测测试用例,支持参数化,跳过特定用例,失败重试等功能. 安装 pip install -U pytest ...
- 详解Redis中两种持久化机制RDB和AOF(面试常问,工作常用)
redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发生丢失.幸好Redis还为我们提供了持久化的机制,分别是RDB(Redis DataBase)和AOF(Ap ...
- 第14章节 BJROBOT karto 算法构建地图【ROS全开源阿克曼转向智能网联无人驾驶车】
建地图前说明:请确保你的小车已经校正好 IMU.角速度.线速度,虚拟机配置好 ROS 网络的前提进行,否则会造成构建地图无边界.虚拟机端无法正常收到小车主控端发布的话题数据等异常情况!! 1.把小车平 ...