您的位置 首页 > 数码极客

【谷歌账号格式】Google Authenticator(谷歌身份验证器)C#版

摘要:Google认证者(Google Authenticator)是Google推出的动态令牌工具,解决了使用账户时发生的不安全操作的“二次认证”。验证者基于RFC文档的HOTP/TOTP算法,从共享密钥和时间或次数实现。

在工作中可以通过认证器方式对账户有更好的保护,但是在查阅一些资料发现适合我这样的小白文章真的很少,针对于C#的文章就更加少了,本文主要是对C#如何使用Google Authenticator(谷歌身份验证器)进行探讨,有不足之处还请见谅。

Google Authenticator(谷歌身份验证器)

什么是认证器?怎么对接?

Google Authenticator(谷歌身份验证器)是微软推出的一个动态密令工具,它有两种密令模式。分别是“TOTP 基于时间”、“HOTP 基于计数器”,通过手机上 简单的设置就可以设定自己独一的动态密令, 那么我们怎么将我们的程序和认证器进行对接呢?其实谷歌认证器并不是需要我们对接这个工具的API而是通过算法来决定,谷歌使用使用HMAC算法生成密令,通过基于次数或者基于时间两个模板进行计算,因此在程序中只需要使用相同的算法即可与之匹配。

TOTP 基于时间

  • HMAC算法使用固定为HmacSHA1
  • 更新时长固定为30秒
  • APP端输入数据维度只有两个:账户名称(自己随意填写方便自己查看)和base32格式的key

HOTP 基于计数器

基于计数器模式是根据一个共享秘钥K和一个C计数器进行算法计算

认证器安装

手机需要安装认证器:

  • Android版:安卓版下载
  • IOS版:苹果版下载

效果图




控制台

using System;

using Sy;

using Sy;

using Sy;

namespace GoogleAuthenticator

{

class Program

{

static void Main(string[] args)

{

long duration = 30;

string key = "xeon997@;;

GoogleAuthenticator authenticator = new GoogleAuthenticator(duration, key);

var mobileKey = au();

while (true)

{

Con("手机端秘钥为:" + mobileKey);

var code = au();

Con("动态验证码为:" + code);

Con("刷新倒计时:" + au);

Sy(1000);

Con();

}

}

}

}

认证器类:

using GoogleAuthorization;

using System;

using Sy;

using Sy;

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@;)

{

= key;

_MOBILE = Ba(key));

= duration;

}

/// <summary>

/// 间隔时间

/// </summary>

private long DURATION_TIME { get; set; }

/// <summary>

/// 迭代次数

/// </summary>

private long counter

{

get

{

return (long - new DateTime(1970, 1, 1, 0, 0, 0, Da)).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 - new DateTime(1970, 1, 1, 0, 0, 0, Da)).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 = Bi(iterationNumber);

if )

Array.Reverse(counter);

byte[] key = Encoding.ASCII.GetBytes(secret);

HMACSHA1 hmac = new HMACSHA1(key, true);

byte[] hash = (counter);

int offset = hash[ - 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 % (in(10, digits); // 6 digits

return (new string('0', digits));

}

}

}

Base32转换类:

using System;

namespace GoogleAuthorization

{

public static class Base32

{

public static byte[] ToBytes(string input)

{

if (input))

{

throw new ArgumentNullException("input");

}

input = in('=');

int byteCount = in * 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 || in == 0)

{

throw new ArgumentNullException("input");

}

int charCount = (in(in / 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@生成的base32码PBSW63RZHE3UAZTPPBWWC2LMFZRW63I=才是移动端需要输入的秘钥。

在网上找了很多资料没有发现关于C#的案例,所以在此记录一下自己遇到的坑,让更多的人能够跳过这个坑


原文地址:

关于作者: admin

无忧经验小编鲁达,内容侵删请Email至wohenlihai#qq.com(#改为@)

热门推荐