计算机网络安全 —— C# 使用谷歌身份验证器(Google Authenticator)(五)
一、Google Authenticator 基本概念
Google Authenticator是谷歌推出的一款动态口令工具,旨在解决大家Google账户遭到恶意攻击的问题,在手机端生成动态口令后,在Google相关的服务登陆中除了用正常用户名和密码外,需要输入一次动态口令才能验证成功,此举是为了保护用户的信息安全。
谷歌验证(Google Authenticator)通过两个验证步骤,在登录时为用户的谷歌帐号提供一层额外的安全保护。使用谷歌验证可以直接在用户的设备上生成动态密码,无需网络连接。其基本步骤如下:
- 使用google authenticator PAM插件为登录账号生成动态验证码。
- 手机安装Google身份验证器,通过此工具扫描上一步生成的二维码图形,获取动态验证码。
当用户在Google帐号中启用“两步验证”功能后,就可以使用Google Authenticator来防止陌生人通过盗取的密码访问用户的帐户。通过两步验证流程登录时,用户需要同时使用密码和通过手机产生的动态密码来验证用户的身份。也就是说,即使可能的入侵者窃取或猜出了用户的密码,也会因不能使用用户的手机而无法登录帐户。
更多原理可以查看阅读“详解Google Authenticator工作原理”。
二、.NET 使用 Google Authenticator
第一步,通过 Nuget 下载 Google Authenticator 安装包
第二步,例如我们要实现这样的功能:手机扫描 PC 生成的二维码,绑定用户信息后,后续使用手机生成的验证码输入到 PC 端进行校验。我们通过编程生成一个二维码如下图所示:
第三步:安装 Google Authenticator APP,安卓版下载、IOS下载(注意:安卓版本下载需翻墙)。安装成功后,扫描上图的二维码添加如下:
第四步:输入生成的验证码,在 PC 端输入口令后,展示校验通过(注意:口令有效时间为30秒)。
Google Authenticator 在 PC 端生成二维码、手机上生成验证码、 PC 端校验验证码,这些过程无需网络,只需要保证 PC 时间和手机时间正确一致即可。
Google Authenticator 工具类代码如下(引用自 https://www.cnblogs.com/denuk/p/11608510.html):
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
161 /// <summary>
162 /// Writes a string into a bitmap
163 /// </summary>
164 /// <param name="qrCodeSetupImageUrl"></param>
165 /// <returns></returns>
166 public static Image GetQRCodeImage(string qrCodeSetupImageUrl)
167 {
168 // data:image/png;base64,
169 qrCodeSetupImageUrl = qrCodeSetupImageUrl.Replace("data:image/png;base64,", "");
170 Image img = null;
171 byte[] buffer = Convert.FromBase64String(qrCodeSetupImageUrl);
172 using (MemoryStream ms = new MemoryStream(buffer))
173 {
174 img = Image.FromStream(ms);
175 }
176 return img;
177 }
178 }
179
180 public class Base32Encoding
181 {
182 /// <summary>
183 /// Base32 encoded string to byte[]
184 /// </summary>
185 /// <param name="input">Base32 encoded string</param>
186 /// <returns>byte[]</returns>
187 public static byte[] ToBytes(string input)
188 {
189 if (string.IsNullOrEmpty(input))
190 {
191 throw new ArgumentNullException("input");
192 }
193
194 input = input.TrimEnd('='); //remove padding characters
195 int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
196 byte[] returnArray = new byte[byteCount];
197
198 byte curByte = 0, bitsRemaining = 8;
199 int mask = 0, arrayIndex = 0;
200
201 foreach (char c in input)
202 {
203 int cValue = CharToValue(c);
204
205 if (bitsRemaining > 5)
206 {
207 mask = cValue << (bitsRemaining - 5);
208 curByte = (byte)(curByte | mask);
209 bitsRemaining -= 5;
210 }
211 else
212 {
213 mask = cValue >> (5 - bitsRemaining);
214 curByte = (byte)(curByte | mask);
215 returnArray[arrayIndex++] = curByte;
216 curByte = (byte)(cValue << (3 + bitsRemaining));
217 bitsRemaining += 3;
218 }
219 }
220
221 //if we didn't end with a full byte
222 if (arrayIndex != byteCount)
223 {
224 returnArray[arrayIndex] = curByte;
225 }
226
227 return returnArray;
228 }
229
230 /// <summary>
231 /// byte[] to Base32 string, if starting from an ordinary string use Encoding.UTF8.GetBytes() to convert it to a byte[]
232 /// </summary>
233 /// <param name="input">byte[] of data to be Base32 encoded</param>
234 /// <returns>Base32 String</returns>
235 public static string ToString(byte[] input)
236 {
237 if (input == null || input.Length == 0)
238 {
239 throw new ArgumentNullException("input");
240 }
241
242 int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
243 char[] returnArray = new char[charCount];
244
245 byte nextChar = 0, bitsRemaining = 5;
246 int arrayIndex = 0;
247
248 foreach (byte b in input)
249 {
250 nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
251 returnArray[arrayIndex++] = ValueToChar(nextChar);
252
253 if (bitsRemaining < 4)
254 {
255 nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
256 returnArray[arrayIndex++] = ValueToChar(nextChar);
257 bitsRemaining += 5;
258 }
259
260 bitsRemaining -= 3;
261 nextChar = (byte)((b << bitsRemaining) & 31);
262 }
263
264 //if we didn't end with a full char
265 if (arrayIndex != charCount)
266 {
267 returnArray[arrayIndex++] = ValueToChar(nextChar);
268 while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
269 }
270
271 return new string(returnArray);
272 }
273
274 private static int CharToValue(char c)
275 {
276 int value = (int)c;
277
278 //65-90 == uppercase letters
279 if (value < 91 && value > 64)
280 {
281 return value - 65;
282 }
283 //50-55 == numbers 2-7
284 if (value < 56 && value > 49)
285 {
286 return value - 24;
287 }
288 //97-122 == lowercase letters
289 if (value < 123 && value > 96)
290 {
291 return value - 97;
292 }
293
294 throw new ArgumentException("Character is not a Base32 character.", "c");
295 }
296
297 private static char ValueToChar(byte b)
298 {
299 if (b < 26)
300 {
301 return (char)(b + 65);
302 }
303
304 if (b < 32)
305 {
306 return (char)(b + 24);
307 }
308
309 throw new ArgumentException("Byte is not a value Base32 value.", "b");
310 }
311 }
测试代码如下:
1 // 密钥
2 private string key = "123456";
3
4 // 生成新的二维码
5 private void ButtonBase_OnClick1(object sender, RoutedEventArgs e)
6 {
7 // 发行人
8 string issuer = TextBoxIssuer.Text;
9
10 //登陆账号名称
11 string user = TextBoxUser.Text;
12
13 // 生成 SetupCode
14 var code = new GoogleAuthenticator().GenerateSetupCode(issuer, user, key, 5);
15
16 // 转换成位图
17 var img = GoogleAuthenticator.GetQRCodeImage(code.QrCodeSetupImageUrl);
18
19 // 展示位图
20 {
21 Bitmap bitmap = img as Bitmap;
22 IntPtr myImagePtr = bitmap.GetHbitmap();
23 ImageSource imgsource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(myImagePtr, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); //创建imgSource
24 ImageQRCode.Source = imgsource;
25 }
26 }
27
28 // 验证校验
29 private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
30 {
31 string token = TextBoxToken.Text;
32 if (string.IsNullOrEmpty(token) == false)
33 {
34 GoogleAuthenticator gat = new GoogleAuthenticator();
35 var result = gat.ValidateTwoFactorPIN(key, token.ToString());
36 if (result)
37 {
38 MessageBox.Show("动态码校验通过!", "提示信息", MessageBoxButton.OK, MessageBoxImage.Question);
39 }
40 else
41 {
42 MessageBox.Show("动态码校验未通过!", "提示信息", MessageBoxButton.OK, MessageBoxImage.Warning);
43 }
44 }
45 }
计算机网络安全 —— C# 使用谷歌身份验证器(Google Authenticator)(五)的更多相关文章
- Google Authenticator(谷歌身份验证器)
<!DOCTYPE html>Google Authenticator(谷歌身份验证器) ] Google Authenticator(谷歌身份验证器) Google Authentica ...
- Google Authenticator(谷歌身份验证器)C#版
摘要:Google Authenticator(谷歌身份验证器),是谷歌公司推出的一款动态令牌工具,解决账户使用时遭到的一些不安全的操作进行的"二次验证",认证器基于RFC文档中的 ...
- 七牛云如何绑定二次验证码_虚拟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 ...
- paypal支付平台如何使用二次验证码_虚拟MFA_两步验证_谷歌身份验证器?
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 paypal支付平台如何使用二次验证码_虚拟MFA_两步验证_谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的优势 ...
- SmartMS如何使用二次验证码/虚拟MFA/两步验证/谷歌身份验证器?
一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接 SmartMS如何使用二次验证码/虚拟MFA/两步验证/谷歌身份验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无 ...
随机推荐
- C#中string类型必填的诡异问题
背景 ASP.NETCore3.0项目,使用Swagger接口文档. 之前的项目都是Swashbuckle.AspNetCore-5.0.0 新项目想尝尝鲜,用最新版Swashbuckle.AspNe ...
- 使用pdf2htmlEX将pdf文件转为html
https://github.com/coolwanglu/pdf2htmlEX 参考github文档,转换出来的的效果貌似很好,可以参考OFFICE 文档转换为html在线预览. pdf2swf 和 ...
- 第十八章节 BJROBOT 安卓手机 APP 建地图【ROS全开源阿克曼转向智能网联无人驾驶车】
1.把小车平放在地板上,用资料里的虚拟机,打开一个终端 ssh 过去主控端启动roslaunch znjrobotbringup.launch 2.在虚拟机端再打开一个终端,ssh 过去主控端启动ro ...
- Linux系统下qt程序运行找不到IGL
linux下使用QT5运行时出现两个问题: cannot find -lGL collect2:error:ld returned 1 exit status 这是因为系统缺少链接库,执行两条命令即可 ...
- sql删除重复数据思路
总的思路就是先找出表中重复数据中的一条数据,插入临时表中,删除所有的重复数据,然后再将临时表中的数据插入表中.所以重点是如何找出重复数据中的一条数据,有三种情况 1.重复数据完全一样,使用distin ...
- 买卖股票的最佳时机 III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你最多可以完成 两笔 交易. 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的 ...
- Java基础-数据类型及变量
Java基本语法 1.标识符(zhi) 含义:名字 类名.对象名.方法名.变量名.常量名-- 一个合法的标识符的组成:数字.字母._和$ 注意事项: 不能重复 不能以数字开头 区分大小写 不能以关键字 ...
- Docker学习笔记之查看Docker
命令: 使用history命令查看镜像历史 使用cp命令复制容器中的文件到主机 使用commit命令把修改过的容器创建为镜像 使用diff命令检查容器文件的修改 使用inspect命令查看容器/镜像详 ...
- Netty源码解析 -- FastThreadLocal与HashedWheelTimer
Netty源码分析系列文章已接近尾声,本文再来分析Netty中两个常见组件:FastThreadLoca与HashedWheelTimer. 源码分析基于Netty 4.1.52 FastThread ...
- 【Java】流程控制 - 顺序结构、 选择(分支)结构(单分支、双分支、多分支、嵌套)、循环结构(for、while、do...while)、跳转语句(break、continue)
流程控制语句结构 文章目录 流程控制语句结构 一. 顺序结构 1. 输出语句 2. 输入语句 3.code 二.复合语句 三. 分支结构 1. 条件判断 1.单分支结构 2.双分支结构 3.多分支结构 ...