02.01 Google Authenticator(谷歌身份驗證器)C#版

摘要:Google Authenticator(谷歌身份驗證器),是谷歌公司推出的一款動態令牌工具,解決賬戶使用時遭到的一些不安全的操作進行的“二次驗證”,認證器基於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版:蘋果版下載

效果圖


Google Authenticator(谷歌身份驗證器)C#版


Google Authenticator(谷歌身份驗證器)C#版


控制檯

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 = "[email protected]";

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>

/// 初始化驗證碼生成規則

///

/// <param>秘鑰(手機使用Base32碼)

/// <param>驗證碼間隔多久刷新一次(默認30秒和google同步)

public GoogleAuthenticator(long duration = 30, string key = "[email protected]")

{

this.SERECT_KEY = key;

this.SERECT_KEY_MOBILE = Base32.ToString(Encoding.UTF8.GetBytes(key));

this.DURATION_TIME = duration;

}

/// <summary>

/// 間隔時間

///

private long DURATION_TIME { get; set; }

/// <summary>

/// 迭代次數

///

private long COUNTER

{

get

{

return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds / DURATION_TIME;

}

}

/// <summary>

/// 秘鑰

///

private string SERECT_KEY { get; set; }

/// <summary>

/// 手機端輸入的秘鑰

///

private string SERECT_KEY_MOBILE { get; set; }

/// <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>

/// 獲取手機端秘鑰

///

/// <returns>

public string GetMobilePhoneKey()

{

if (SERECT_KEY_MOBILE == null)

throw new ArgumentNullException("SERECT_KEY_MOBILE");

return SERECT_KEY_MOBILE;

}

/// <summary>

/// 生成認證碼

///

/// <returns>返回驗證碼/<returns>

public string GenerateCode()

{

return GenerateHashedCode(SERECT_KEY, COUNTER);

}

/// <summary>

/// 按照次數生成哈希編碼

///

/// <param>秘鑰

/// <param>迭代次數

/// <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轉碼得到的,而程序端是直接輸入源碼。

如原秘鑰為[email protected]生成的base32碼PBSW63RZHE3UAZTPPBWWC2LMFZRW63I=才是移動端需要輸入的秘鑰。

在網上找了很多資料沒有發現關於C#的案例,所以在此記錄一下自己遇到的坑,讓更多的人能夠跳過這個坑


原文地址:

https://www.cnblogs.com/easyauthor/p/11054869.html


分享到:


相關文章: