c# 使用谷歌身份驗(yàn)證GoogleAuthenticator的示例
此功能相當(dāng)于給系統(tǒng)加了個(gè)令牌,只有輸入對的一組數(shù)字才可以驗(yàn)證成功。類似于QQ令牌一樣。
一丶創(chuàng)建最核心的一個(gè)類GoogleAuthenticator
此類包含了生成密鑰,驗(yàn)證,將綁定密鑰轉(zhuǎn)為二維碼。
public class GoogleAuthenticator
{
private readonly static DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private TimeSpan DefaultClockDriftTolerance { get; set; }
public GoogleAuthenticator()
{
DefaultClockDriftTolerance = TimeSpan.FromMinutes(5);
}
/// <summary>
/// Generate a setup code for a Google Authenticator user to scan
/// </summary>
/// <param name="issuer">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 </param>
/// <param name="accountTitleNoSpaces">Account Title (no spaces)</param>
/// <param name="accountSecretKey">Account Secret Key</param>
/// <param name="QRPixelsPerModule">Number of pixels per QR Module (2 pixels give ~ 100x100px QRCode)</param>
/// <returns>SetupCode object</returns>
public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, int QRPixelsPerModule)
{
byte[] key = Encoding.UTF8.GetBytes(accountSecretKey);
return GenerateSetupCode(issuer, accountTitleNoSpaces, key, QRPixelsPerModule);
}
/// <summary>
/// Generate a setup code for a Google Authenticator user to scan
/// </summary>
/// <param name="issuer">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 </param>
/// <param name="accountTitleNoSpaces">Account Title (no spaces)</param>
/// <param name="accountSecretKey">Account Secret Key as byte[]</param>
/// <param name="QRPixelsPerModule">Number of pixels per QR Module (2 = ~120x120px QRCode)</param>
/// <returns>SetupCode object</returns>
public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, byte[] accountSecretKey, int QRPixelsPerModule)
{
if (accountTitleNoSpaces == null) { throw new NullReferenceException("Account Title is null"); }
accountTitleNoSpaces = RemoveWhitespace(accountTitleNoSpaces);
string encodedSecretKey = Base32Encoding.ToString(accountSecretKey);
string provisionUrl = null;
provisionUrl = String.Format("otpauth://totp/{2}:{0}?secret={1}&issuer={2}", accountTitleNoSpaces, encodedSecretKey.Replace("=",""), UrlEncode(issuer));
using (QRCodeGenerator qrGenerator = new QRCodeGenerator())
using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(provisionUrl, QRCodeGenerator.ECCLevel.M))
using (QRCode qrCode = new QRCode(qrCodeData))
using (Bitmap qrCodeImage = qrCode.GetGraphic(QRPixelsPerModule))
using (MemoryStream ms = new MemoryStream())
{
qrCodeImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
return new SetupCode(accountTitleNoSpaces, encodedSecretKey, String.Format("data:image/png;base64,{0}", Convert.ToBase64String(ms.ToArray())));
}
}
private static string RemoveWhitespace(string str)
{
return new string(str.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
private string UrlEncode(string value)
{
StringBuilder result = new StringBuilder();
string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
foreach (char symbol in value)
{
if (validChars.IndexOf(symbol) != -1)
{
result.Append(symbol);
}
else
{
result.Append('%' + String.Format("{0:X2}", (int)symbol));
}
}
return result.ToString().Replace(" ", "%20");
}
public string GeneratePINAtInterval(string accountSecretKey, long counter, int digits = 6)
{
return GenerateHashedCode(accountSecretKey, counter, digits);
}
internal string GenerateHashedCode(string secret, long iterationNumber, int digits = 6)
{
byte[] key = Encoding.UTF8.GetBytes(secret);
return GenerateHashedCode(key, iterationNumber, digits);
}
internal string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6)
{
byte[] counter = BitConverter.GetBytes(iterationNumber);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(counter);
}
HMACSHA1 hmac = new HMACSHA1(key);
byte[] hash = hmac.ComputeHash(counter);
int offset = hash[hash.Length - 1] & 0xf;
// Convert the 4 bytes into an integer, ignoring the sign.
int binary =
((hash[offset] & 0x7f) << 24)
| (hash[offset + 1] << 16)
| (hash[offset + 2] << 8)
| (hash[offset + 3]);
int password = binary % (int)Math.Pow(10, digits);
return password.ToString(new string('0', digits));
}
private long GetCurrentCounter()
{
return GetCurrentCounter(DateTime.UtcNow, _epoch, 30);
}
private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep)
{
return (long)(now - epoch).TotalSeconds / timeStep;
}
public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient)
{
return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance);
}
public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance)
{
var codes = GetCurrentPINs(accountSecretKey, timeTolerance);
return codes.Any(c => c == twoFactorCodeFromClient);
}
public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance)
{
List<string> codes = new List<string>();
long iterationCounter = GetCurrentCounter();
int iterationOffset = 0;
if (timeTolerance.TotalSeconds > 30)
{
iterationOffset = Convert.ToInt32(timeTolerance.TotalSeconds / 30.00);
}
long iterationStart = iterationCounter - iterationOffset;
long iterationEnd = iterationCounter + iterationOffset;
for (long counter = iterationStart; counter <= iterationEnd; counter++)
{
codes.Add(GeneratePINAtInterval(accountSecretKey, counter));
}
return codes.ToArray();
}
}
其中GenerateSetupCode 這個(gè)方法是用于把綁定的密鑰直接轉(zhuǎn)成二維碼圖片,然后再轉(zhuǎn)成base64圖片 輸出再頁面上,這樣在APP上直接用掃一掃即可綁定。
二丶由于生成的密鑰不可以直接使用,需要進(jìn)行Base32進(jìn)行編碼。下面是Base32Encoding類
public class Base32Encoding
{
/// <summary>
/// Base32 encoded string to byte[]
/// </summary>
/// <param name="input">Base32 encoded string</param>
/// <returns>byte[]</returns>
public static byte[] ToBytes(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException("input");
}
input = input.TrimEnd('='); //remove padding characters
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
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 we didn't end with a full byte
if (arrayIndex != byteCount)
{
returnArray[arrayIndex] = curByte;
}
return returnArray;
}
/// <summary>
/// byte[] to Base32 string, if starting from an ordinary string use Encoding.UTF8.GetBytes() to convert it to a byte[]
/// </summary>
/// <param name="input">byte[] of data to be Base32 encoded</param>
/// <returns>Base32 String</returns>
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 we didn't end with a full char
if (arrayIndex != charCount)
{
returnArray[arrayIndex++] = ValueToChar(nextChar);
while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
}
return new string(returnArray);
}
private static int CharToValue(char c)
{
int value = (int)c;
//65-90 == uppercase letters
if (value < 91 && value > 64)
{
return value - 65;
}
//50-55 == numbers 2-7
if (value < 56 && value > 49)
{
return value - 24;
}
//97-122 == lowercase letters
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");
}
}
三丶主程序里面直接調(diào)用方法
private SetupCode Google(string key, string Guids)
{
GoogleAuthenticator gat = new GoogleAuthenticator();
return gat.GenerateSetupCode("Supported Giving", key, Guids, 5);
}
//key系統(tǒng)的賬號(hào),Guid是進(jìn)行加密的字符串,要求唯一,不然密鑰會(huì)重復(fù),所以這里使用Guid. 2為二維碼的大小約120x120px。
SetupCode結(jié)果類為
public class SetupCode
{
public string Account { get; internal set; }
public string AccountSecretKey { get; internal set; }
public string ManualEntryKey { get; internal set; }
/// <summary>
/// Base64-encoded PNG image
/// </summary>
public string QrCodeSetupImageUrl { get; internal set; }
}
ManualEntryKey 是手機(jī)綁定的密鑰。如果想手動(dòng)輸入密鑰綁定就使用此字符串。
QrCodeSetupImageUrl 是將密鑰轉(zhuǎn)成的二維碼圖片
下載這個(gè)APP

進(jìn)入APP后直接綁定,就會(huì)出現(xiàn)一下界面,即為綁定成功,然后我們就可以使用此令牌驗(yàn)證了。

驗(yàn)證方法
//Guids 之前生成密鑰的字符,此時(shí)當(dāng)做唯一鍵來查詢,CheckCode為手機(jī)上動(dòng)態(tài)的6位驗(yàn)證嗎。校驗(yàn)成功會(huì)返回true
GoogleAuthenticator gat = new GoogleAuthenticator();
var result = gat.ValidateTwoFactorPIN(parameters["Guids"].ToString(), parameters["CheckCode"].ToString());
if (result)
{
return "True";
}
else
{
return "False";
}
這樣功能就完成了。
以上就是c# 使用谷歌身份驗(yàn)證GoogleAuthenticator的示例的詳細(xì)內(nèi)容,更多關(guān)于c# 使用谷歌身份驗(yàn)證GoogleAuthenticator的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
c#標(biāo)準(zhǔn)idispose模式使用示例
下面將把C#里實(shí)現(xiàn)IDispose模式的代碼展現(xiàn)出來,大家一起來學(xué)習(xí)一下,它的使用場合也很多的,當(dāng)我們手動(dòng)對網(wǎng)站,數(shù)據(jù)庫作封裝時(shí),都會(huì)用的到2014-02-02
C#使用正則表達(dá)式隱藏手機(jī)號(hào)中間四位為*
這篇文章主要介紹了C#使用正則表達(dá)式隱藏手機(jī)號(hào)中間四位為*的相關(guān)資料,需要的朋友可以參考下2017-06-06
快速了解如何在.NETCORE中使用Generic-Host建立主機(jī)
這篇文章主要介紹了如何在.NETCORE中使用Generic-Host建立主機(jī),文中代碼非常詳細(xì),可供大家參考,感興趣的朋友不妨閱讀完2020-05-05
C#實(shí)現(xiàn)關(guān)閉其他程序窗口或進(jìn)程代碼分享
這篇文章主要介紹了C#實(shí)現(xiàn)關(guān)閉其他程序窗口或進(jìn)程代碼分享,本文給出了兩種方法,并分別給出示例代碼,需要的朋友可以參考下2015-06-06
C# 執(zhí)行CMD命令并接收返回結(jié)果的操作方式
這篇文章主要介紹了C# 執(zhí)行CMD命令并接收返回結(jié)果的操作方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04

