Google Authenticator(谷歌身份验证器)C#版
Google Authenticator(谷歌身份验证器)
什么是认证器?怎么对接?
Google Authenticator(谷歌身份验证器)是微软推出的一个动态密令工具,它有两种密令模式。分别是“TOTP 基于时间”、“HOTP 基于计数器”,通过手机上 简单的设置就可以设定自己独一的动态密令, 那么我们怎么将我们的程序和认证器进行对接呢?其实谷歌认证器并不是需要我们对接这个工具的API而是通过算法来决定,谷歌使用使用HMAC算法生成密令,通过基于次数或者基于时间两个模板进行计算,因此在程序中只需要使用相同的算法即可与之匹配。
TOTP 基于时间
- HMAC算法使用固定为HmacSHA1
- 更新时长固定为30秒
- APP端输入数据维度只有两个:账户名称(自己随意填写方便自己查看)和base32格式的key
HOTP 基于计数器
基于计数器模式是根据一个共享秘钥K和一个C计数器进行算法计算
认证器安装
效果图

容
案例
控制台
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace GoogleAuthenticator
{
class Program
{
static void Main(string[] args)
{
long duration = ;
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();
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 = , 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(, , , , , , 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(, , , , , , 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 = )
{
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 - ] & 0xf; int binary =
((hash[offset] & 0x7f) << )
| ((hash[offset + ] & 0xff) << )
| ((hash[offset + ] & 0xff) << )
| (hash[offset + ] & 0xff); int password = binary % (int)Math.Pow(, digits); // 6 digits return password.ToString(new string('', 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 * / ;
byte[] returnArray = new byte[byteCount]; byte curByte = , bitsRemaining = ;
int mask = , arrayIndex = ; foreach (char c in input)
{
int cValue = CharToValue(c); if (bitsRemaining > )
{
mask = cValue << (bitsRemaining - );
curByte = (byte)(curByte | mask);
bitsRemaining -= ;
}
else
{
mask = cValue >> ( - bitsRemaining);
curByte = (byte)(curByte | mask);
returnArray[arrayIndex++] = curByte;
curByte = (byte)(cValue << ( + bitsRemaining));
bitsRemaining += ;
}
} if (arrayIndex != byteCount)
{
returnArray[arrayIndex] = curByte;
} return returnArray;
} public static string ToString(byte[] input)
{
if (input == null || input.Length == )
{
throw new ArgumentNullException("input");
} int charCount = (int)Math.Ceiling(input.Length / 5d) * ;
char[] returnArray = new char[charCount]; byte nextChar = , bitsRemaining = ;
int arrayIndex = ; foreach (byte b in input)
{
nextChar = (byte)(nextChar | (b >> ( - bitsRemaining)));
returnArray[arrayIndex++] = ValueToChar(nextChar); if (bitsRemaining < )
{
nextChar = (byte)((b >> ( - bitsRemaining)) & );
returnArray[arrayIndex++] = ValueToChar(nextChar);
bitsRemaining += ;
} bitsRemaining -= ;
nextChar = (byte)((b << bitsRemaining) & );
} 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 < && value > )
{
return value - ;
}
if (value < && value > )
{
return value - ;
}
if (value < && value > )
{
return value - ;
} throw new ArgumentException("Character is not a Base32 character.", "c");
} private static char ValueToChar(byte b)
{
if (b < )
{
return (char)(b + );
} if (b < )
{
return (char)(b + );
} throw new ArgumentException("Byte is not a value Base32 value.", "b");
}
}
}
总结
需要注意的坑
移动端下载的认证器的秘钥key是通过base32转码得到的,而程序端是直接输入源码。
如原秘钥为xeon997@foxmail.com生成的base32码PBSW63RZHE3UAZTPPBWWC2LMFZRW63I=才是移动端需要输入的秘钥。
在网上找了很多资料没有发现关于C#的案例,所以在此记录一下自己遇到的坑,让更多的人能够跳过这个坑。
案例地址:
git:https://github.com/CN-Yi/GoogleAuthenticator
gitee:https://gitee.com/hsyi/GoogleAuthenticator
在此感谢提供学习资料的大神们,如果有错误的地方欢迎留言。
Google Authenticator(谷歌身份验证器)C#版的更多相关文章
- Google Authenticator(谷歌身份验证器)
<!DOCTYPE html>Google Authenticator(谷歌身份验证器) ] Google Authenticator(谷歌身份验证器) Google Authentica ...
- Google authenticator 谷歌身份验证,实现动态口令
Google authenticator 谷歌身份验证,实现动态口令 google authenticator php 服务端 使用PHP类 require_once '../PHPGangsta/G ...
- 七牛云如何绑定二次验证码_虚拟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.无 ...
随机推荐
- Browser Screen
Screen对象 Screen对象包含有关客户端显示屏幕的信息. 注释:没有应用于screen对象的公开标准,不过所有浏览器都支持该对象. Screen对象属性 availHeight 返回显示屏幕的 ...
- cocos2d-x 学习资料汇总
cocos2d-x配置问题 - 我要飞的更高 - 博客频道 - CSDN.NET Cocos2d-x win7 + vs2010 配置图文详解(亲测) - 子龙山人 - 博客园 WINDONWS7+V ...
- 在MVC中使用NHibernate学习记录
NHibernate简介: NHibernate是一个面向.net环境的对象/关系数据库映射工具,对象/关系数据库映射(object/relational mapping,ORM)是一种技术,可以将对 ...
- DBMS事务的四大特性
数据库事务的四大特性分别是:原子性.一致性.隔离性和持久性. 特性 说明 实现 一致性 在一个事务执行之前和执行之后数据库都必须处于一致性状态.假如数据库的状态满足所有的完整性约束,也可以说数据库是一 ...
- echarts 相关属性介绍
title: {//图表标题 x: 'left', //组件离容器左侧的距离,left的值可以是像20,这样的具体像素值, 可以是像 '20%' 这样相对于容器高宽的百分比,也可以是 'lef ...
- 如何解决EXCEL中的科学计数法
EXCEL虽然能够有效的处理数据,尤其是数字的计算.但是,在单元格中输入数字的时候,很多时候都会受到科学计算法的困扰. 当单元格中输入的数字,超过11位时,就会自动变成科学计数法.无论您怎么调整列的宽 ...
- 阿里云服务器下安装LAMP环境(CentOS Linux 6.3)(1)
阿里的云服务器准备好以后,我们首先要做的就是把自己购买的磁盘空间挂载到系统里面,我们为服务器选择的是 Linux 系统,确切说的是 CentOS 系统. 默认阿里云服务器带了一个 20G 的空间,一般 ...
- Redis学习记录(一)
在学习Redis之前,要知道什么是NoSQL? 1.NoSQL 1.1. 什么是NoSQL NoSQL(NoSQL = Not Only SQL),表示“不仅仅是SQL”,泛指非关系型数据库. 1.2 ...
- opencv anaconda
from: http://blog.csdn.net/fairylrt/article/details/43560525 Anaconda是一个python的一个包装,或者不单单是这样.你可以认为An ...
- 基础篇(2):c++顺序结构程序设计
一个程序最基本的结构莫过于3种:顺序,选择,循环.这篇讲讲顺序结构. c++语言的运算符与表达式数量之多,在高级语言中是少见的,也使得它的语言功能十分完善. c++的运算符有单目与双目之分(作用于一个 ...