摘要:Google Authenticator(谷歌身份验证器),是谷歌公司推出的一款动态令牌工具,解决账户使用时遭到的一些不安全的操作进行的“二次验证”,认证器基于RFC文档中的HOTP/TOTP算法实现 ,是一种从共享秘钥和时间或次数一次性令牌的算法谷歌地图下载助手。在工作中可以通过认证器方式对账户有更好的保护,但是在查阅一些资料发现适合我这样的小白文章真的很少,针对于C#的文章就更加少了,本文主要是对C#如何使用Google Authenticator(谷歌身份验证器)进行探讨,有不足之处还请见谅。
Google Authenticator(谷歌身份验证器)什么是认证器?怎么对接?Google Authenticator(谷歌身份验证器)是微软推出的一个动态密令工具,它有两种密令模式谷歌地图下载助手。分别是“TOTP 基于时间”、“HOTP 基于计数器”,通过手机上 简单的设置就可以设定自己独一的动态密令, 那么我们怎么将我们的程序和认证器进行对接呢?其实谷歌认证器并不是需要我们对接这个工具的API而是通过算法来决定,谷歌使用使用HMAC算法生成密令,通过基于次数或者基于时间两个模板进行计算,因此在程序中只需要使用相同的算法即可与之匹配。
TOTP 基于时间HMAC算法使用固定为HmacSHA1更新时长固定为30秒APP端输入数据维度只有两个:账户名称(自己随意填写方便自己查看)和base32格式的keyHOTP 基于计数器基于计数器模式是根据一个共享秘钥K和一个C计数器进行算法计算
认证器安装手机需要安装认证器:
Android版:安卓版下载IOS版:苹果版下载效果图
控制台using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace GoogleAuthenticator
class Program
static void Main(string[] args)
long duration = 30;
string key = "xeon997@foxmail.com"
GoogleAuthenticator authenticator = new GoogleAuthenticator(duration, key);
var mobileKey = authenticator.GetMobilePhoneKey();
while (true)
Console.WriteLine("手机端秘钥为:" + mobileKey);
var code = authenticator.GenerateCode();
Console.WriteLine("动态验证码为:" + code);
Console.WriteLine("刷新倒计时:" + authenticator.EXPIRE_SECONDS);
System.Threading.Thread.Sleep(1000);
Console.Clear();
认证器类:
using GoogleAuthorization;
using System;
using System.Security.Cryptography;
using System.Text;
namespace GoogleAuthenticator
public class GoogleAuthenticator
/// summary
/// 初始化验证码生成规则
/// /summary
/// param name="key"秘钥(手机使用Base32码)/param
/// param name="duration"验证码间隔多久刷新一次(默认30秒和google同步)/param
public GoogleAuthenticator(long duration = 30, string key = "xeon997@foxmail.com")
this.SERECT_KEY = key;
this.SERECT_KEY_MOBILE = Base32.ToString(Encoding.UTF8.GetBytes(key));
this.DURATION_TIME = duration;
/// summary
/// 间隔时间
/// /summary
private long DURATION_TIME { get; set; }
/// summary
/// 迭代次数
/// /summary
private long COUNTER
get
return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds / DURATION_TIME;
/// summary
/// 秘钥
/// /summary
private string SERECT_KEY { get; set; }
/// summary
/// 手机端输入的秘钥
/// /summary
private string SERECT_KEY_MOBILE { get; set; }
/// summary
/// 到期秒数
/// /summary
public long EXPIRE_SECONDS
get
return (DURATION_TIME - (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds % DURATION_TIME);
/// summary
/// 获取手机端秘钥
/// /summary
/// returns/returns
public string GetMobilePhoneKey()
if (SERECT_KEY_MOBILE == null)
throw new ArgumentNullException("SERECT_KEY_MOBILE");
return SERECT_KEY_MOBILE;
/// summary
/// 生成认证码
/// /summary
/// returns返回验证码/returns
public string GenerateCode()
return GenerateHashedCode(SERECT_KEY, COUNTER);
/// summary
/// 按照次数生成哈希编码
/// /summary
/// param name="secret"秘钥/param
/// param name="iterationNumber"迭代次数/param
/// param name="digits"生成位数/param
/// returns返回验证码/returns
private string GenerateHashedCode(string secret, long iterationNumber, int digits = 6)
byte[] counter = BitConverter.GetBytes(iterationNumber);
if (BitConverter.IsLittleEndian)
Array.Reverse(counter);
byte[] key = Encoding.ASCII.GetBytes(secret);
HMACSHA1 hmac = new HMACSHA1(key, true);
byte[] hash = hmac.ComputeHash(counter);
int offset = hash[hash.Length - 1] 0xf;
int binary =
((hash[offset] 0x7f) 24)
| ((hash[offset + 1] 0xff) 16)
| ((hash[offset + 2] 0xff) 8)
| (hash[offset + 3] 0xff);
int password = binary % (int)Math.Pow(10, digits); // 6 digits
return password.ToString(new string('0', digits));
Base32转换类:
using System;
namespace GoogleAuthorization
public static class Base32
public static byte[] ToBytes(string input)
if (string.IsNullOrEmpty(input))
throw new ArgumentNullException("input");
input = input.TrimEnd('=');
int byteCount = input.Length * 5 / 8;
byte[] returnArray = new byte[byteCount];
byte curByte = 0, bitsRemaining = 8;
int mask = 0, arrayIndex = 0;
foreach (char c in input)
int cValue = CharToValue(c);
if (bitsRemaining 5)
mask = cValue (bitsRemaining - 5);
curByte = (byte)(curByte | mask);
bitsRemaining -= 5;
else
mask = cValue (5 - bitsRemaining);
curByte = (byte)(curByte | mask);
returnArray[arrayIndex++] = curByte;
curByte = (byte)(cValue (3 + bitsRemaining));
bitsRemaining += 3;
if (arrayIndex != byteCount)
returnArray[arrayIndex] = curByte;
return returnArray;
public static string ToString(byte[] input)
if (input == null || input.Length == 0)
throw new ArgumentNullException("input");
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
char[] returnArray = new char[charCount];
byte nextChar = 0, bitsRemaining = 5;
int arrayIndex = 0;
foreach (byte b in input)
nextChar = (byte)(nextChar | (b (8 - bitsRemaining)));
returnArray[arrayIndex++] = ValueToChar(nextChar);
if (bitsRemaining 4)
nextChar = (byte)((b (3 - bitsRemaining)) 31);
returnArray[arrayIndex++] = ValueToChar(nextChar);
bitsRemaining += 5;
bitsRemaining -= 3;
nextChar = (byte)((b bitsRemaining) 31);
if (arrayIndex != charCount)
returnArray[arrayIndex++] = ValueToChar(nextChar);
while (arrayIndex != charCount) returnArray[arrayIndex++] = '='
return new string(returnArray);
private static int CharToValue(char c)
var value = (int)c;
if (value 91 value 64)
return value - 65;
if (value 56 value 49)
return value - 24;
if (value 123 value 96)
return value - 97;
throw new ArgumentException("Character is not a Base32 character.", "c");
private static char ValueToChar(byte b)
if (b 26)
return (char)(b + 65);
if (b 32)
return (char)(b + 24);
throw new ArgumentException("Byte is not a value Base32 value.", "b");
总结需要注意的坑移动端下载的认证器的秘钥key是通过base32转码得到的,而程序端是直接输入源码谷歌地图下载助手。
如原秘钥为xeon997@foxmail.com生成的base32码PBSW63RZHE3UAZTPPBWWC2LMFZRW63I=才是移动端需要输入的秘钥谷歌地图下载助手。
在网上找了很多资料没有发现关于C#的案例谷歌地图下载助手,所以在此记录一下自己遇到的坑,让更多的人能够跳过这个坑
原文地址:
添加上方▲技术, 在线咨询
复制微信号
声明
一、本站原创内容,其版权属于本网站所有。其他媒体、网站或个人转载使用时不得进行商业性的原版原式的转载,也不得歪曲和篡改本网站所发布的内容。如转载须注明文章来源。
二、本网站转载其它媒体作品的目的在于传递更多信息,并不代表本网站赞同其观点和对其真实性负责;如侵犯你的权益请告诉我们立即删除;其他媒体、网站或个人转载使用自负法律责任。
发表评论
2022-06-14 18:27:36回复