c# 使用谷歌身份驗證GoogleAuthenticator的示例
此功能相當于給系統(tǒng)加了個令牌,只有輸入對的一組數(shù)字才可以驗證成功。類似于QQ令牌一樣。
一丶創(chuàng)建最核心的一個類GoogleAuthenticator
此類包含了生成密鑰,驗證,將綁定密鑰轉為二維碼。
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 這個方法是用于把綁定的密鑰直接轉成二維碼圖片,然后再轉成base64圖片 輸出再頁面上,這樣在APP上直接用掃一掃即可綁定。
二丶由于生成的密鑰不可以直接使用,需要進行Base32進行編碼。下面是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)的賬號,Guid是進行加密的字符串,要求唯一,不然密鑰會重復,所以這里使用Guid. 2為二維碼的大小約120x120px。
SetupCode結果類為
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 是手機綁定的密鑰。如果想手動輸入密鑰綁定就使用此字符串。
QrCodeSetupImageUrl 是將密鑰轉成的二維碼圖片
下載這個APP
進入APP后直接綁定,就會出現(xiàn)一下界面,即為綁定成功,然后我們就可以使用此令牌驗證了。
驗證方法
//Guids 之前生成密鑰的字符,此時當做唯一鍵來查詢,CheckCode為手機上動態(tài)的6位驗證嗎。校驗成功會返回true
GoogleAuthenticator gat = new GoogleAuthenticator(); var result = gat.ValidateTwoFactorPIN(parameters["Guids"].ToString(), parameters["CheckCode"].ToString()); if (result) { return "True"; } else { return "False"; }
這樣功能就完成了。
以上就是c# 使用谷歌身份驗證GoogleAuthenticator的示例的詳細內(nèi)容,更多關于c# 使用谷歌身份驗證GoogleAuthenticator的資料請關注腳本之家其它相關文章!
相關文章
基于c#圖像灰度化、灰度反轉、二值化的實現(xiàn)方法詳解
本篇文章是對c#圖像灰度化、灰度反轉、二值化的實現(xiàn)方法進行了詳細的分析介紹,需要的朋友參考下2013-05-05C#使用foreach遍歷哈希表(hashtable)的方法
這篇文章主要介紹了C#使用foreach遍歷哈希表(hashtable)的方法,是C#中foreach語句遍歷散列表的典型應用,非常具有實用價值,需要的朋友可以參考下2015-04-04