一、Google Authenticator 基本概念

Google Authenticator是谷歌推出的一款动态口令工具,旨在解决大家Google账户遭到恶意攻击的问题,在手机端生成动态口令后,在Google相关的服务登陆中除了用正常用户名和密码外,需要输入一次动态口令才能验证成功,此举是为了保护用户的信息安全。

谷歌验证(Google Authenticator)通过两个验证步骤,在登录时为用户的谷歌帐号提供一层额外的安全保护。使用谷歌验证可以直接在用户的设备上生成动态密码,无需网络连接。其基本步骤如下:

  1. 使用google authenticator PAM插件为登录账号生成动态验证码。
  2. 手机安装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 ///   12 /// Generate a setup code for a Google Authenticator user to scan  13 ///   14 /// 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   15 /// Account Title (no spaces)  16 /// Account Secret Key  17 /// Number of pixels per QR Module (2 pixels give ~ 100x100px QRCode)  18 /// SetupCode object  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 ///   26 /// Generate a setup code for a Google Authenticator user to scan  27 ///   28 /// 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   29 /// Account Title (no spaces)  30 /// Account Secret Key as byte[]  31 /// Number of pixels per QR Module (2 = ~120x120px QRCode)  32 /// SetupCode object  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 ///  162 /// Writes a string into a bitmap 163 ///  164 ///  165 ///  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 ///  183 /// Base32 encoded string to byte[] 184 ///  185 /// Base32 encoded string 186 /// byte[] 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 ///  231 /// byte[] to Base32 string, if starting from an ordinary string use Encoding.UTF8.GetBytes() to convert it to a byte[] 232 ///  233 /// byte[] of data to be Base32 encoded 234 /// Base32 String 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)的更多相关文章

  1. 改进你的c#代码的5个技巧(四)

    像每一篇文章一样,我会重复几行。我在我的Core i3 CPU、4GB主内存和Windows 7平台上测试了以下代码......

  2. C# 使用Socket链接Ftp服务器下载上传代码FTPClient

    C#操作FTP的类,Socket实现,网上找到的,整理了一下,处理了一些BUG,喜欢的拿去用,但不保证全部BUG已捉......

  3. C# AE之返回上一级和下一级的实战操作

    我就废话不多说了,大家还是直接看代码吧~try{//判断是否可以返回上一视图if (mapControl.Activ......

  4. C#微信公众号推送消息接口消息排重

    用户在微信公众号发送文本,语音,图片等的普通消息时,微信服务器会向公众号配置的接收消息的地址转发用户消息,微信服务器......

  5. C#中DataGridView导出Excel的两种方法

    第一种是用数据流导出:#regionSaveFileDialog saveFileDialog = new Save......

  6. c#定时执行程序代码

    在一般的项目中我们很少用到c#实现每隔规定时间自动执行程序代码,但是如果你经历的项目多,或者应用程序做的比较多的话,......

  7. c#里面的AES加密解密

    C#, Java, PHP, Python和Javascript几种语言的AES加密解密实现更多1AESJavasc......

  8. C# Aspose.Words 删除word中的图片操作

    今天介绍下 Aspose.Words 对 word 中的图片进行删除string tempFile = Applic......

  9. 计算机网络安全 —— C# 使用谷歌身份验证器(Google Authenticator)

    一、Google Authenticator 基本概念Google Authenticator是谷歌推出的一款动态口......

  10. 基于C#的百度图片批量下载工具

    using System; using System.Collections.Generic; using Sy......

随机推荐

  1. 在nodejs中创建cluster

    目录简介cluster集群cluster详解cluster中的eventcluster中的方法cluster中的属性......

  2. Vue 事件的$event参数=事件的值案例

    templateScript 部分export default {data() {return {dataList:......

  3. python 图像增强算法实现详解

    使用python编写了共六种图像增强算法:1)基于直方图均衡化2)基于拉普拉斯算子3)基于对数变换4)基于伽马变换5......

  4. python实现简单的井字棋游戏(gui界面)

    项目输出项目先决条件要使用python构建井字游戏,我们需要tkinter模块和python的基本概念Tkinter......

  5. Java中获取时间戳

    Java中获取时间戳 三种方式对比**最近项目开发过程中发现了项目中获取时间戳的业务。而获取时间戳有以下三种方式,首......

  6. Java 给Word不同页面设置不同背景

    Word文档中,可直接通过【设计】-【页面颜色】页面颜色,通过Java代码可参考如下设置方法:1. 设置单一颜色背景......

  7. JVM虚拟机Class类文件研究分析

    本文对java编译后的Class文件进行研究,依次介绍了Class文件中魔数、JDK版本号、常量池、访问标志、类索引......

  8. python 实现客户端与服务端的通信

    函数介绍Socket对象方法:服务端:函数描述.bind()绑定地址关键字,AF_INET下以元组的形式表示地址。常......

  9. 如何在 Vue 表单中处理图片

    问题:我在 Vue 中有一个 form 表单,用于上传博客帖子,它有标题、正文、描述、片段和图片等范围。所有的一切都......

  10. Perl 使用 Mail::POP3Client 发送邮件

    use Mail::POP3Client;$mail = new Mail::POP3Client("us......