Google Authenticator是谷歌推出的一款動態口令工具,旨在解決你們Google帳戶遭到惡意攻擊的問題,在手機端生成動態口令後,在Google相關的服務登錄中除了用正經常使用戶名和密碼外,須要輸入一次動態口令才能驗證成功,此舉是爲了保護用戶的信息安全。html
谷歌驗證(Google Authenticator)經過兩個驗證步驟,在登陸時爲用戶的谷歌賬號提供一層額外的安全保護。使用谷歌驗證能夠直接在用戶的設備上生成動態密碼,無需網絡鏈接。其基本步驟以下:git
當用戶在Google賬號中啓用「兩步驗證」功能後,就可使用Google Authenticator來防止陌生人經過盜取的密碼訪問用戶的賬戶。經過兩步驗證流程登陸時,用戶須要同時使用密碼和經過手機產生的動態密碼來驗證用戶的身份。也就是說,即便可能的入侵者竊取或猜出了用戶的密碼,也會因不能使用用戶的手機而沒法登陸賬戶。github
更多原理能夠查看閱讀「詳解Google Authenticator工做原理」。安全
經過 Nuget 下載 Google Authenticator 安裝包,Google Authenticator 在 PC 端生成二維碼、手機上生成驗證碼、 PC 端校驗驗證碼,這些過程無需網絡,只須要保證 PC 時間和手機時間正確一致便可。網絡
Google Authenticator 工具類代碼以下(引用自 http://www.javashuo.com/article/p-kxwweixc-vc.html):asp.net
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; //otpauth://totp/Google:yourname@gmail.com?secret=xxxx&issuer=Google 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(); } /// <summary> /// Writes a string into a bitmap /// </summary> /// <param name="qrCodeSetupImageUrl"></param> /// <returns></returns> public static Image GetQRCodeImage(string qrCodeSetupImageUrl) { // data:image/png;base64, qrCodeSetupImageUrl = qrCodeSetupImageUrl.Replace("data:image/png;base64,", ""); Image img = null; byte[] buffer = Convert.FromBase64String(qrCodeSetupImageUrl); using (MemoryStream ms = new MemoryStream(buffer)) { img = Image.FromStream(ms); } return img; } } 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"); } }
使用和驗證:默認的index.cshtml工具
@page @model IndexModel @{ ViewData["Title"] = "Home page"; string key = "123456"; string issuer = "Google"; string user = "Gavin@gamil.com"; // 生成 SetupCode var code = new GoogleAuthenticator().GenerateSetupCode(issuer, user, key, 5); } <div class="text-center"> <h1 class="display-4">GoogleAuthenticator</h1><br /> <p>Account:@code.Account</p><br/> <p>Key:@code.ManualEntryKey</p><br /> <img src="@code.QrCodeSetupImageUrl"/> </div>
新建token.cshtml來驗證token:gitlab
@page @model GoogleAuthenticatorTest.Pages.tokenModel @{ ViewData["Title"] = "Home page"; string key = "123456"; string token = HttpContext.Request.Query["token"]; string result = "token驗證失敗"; if (!string.IsNullOrEmpty(token)){ GoogleAuthenticator gat = new GoogleAuthenticator(); if (gat.ValidateTwoFactorPIN(key, token)) { result = "token驗證成功"; } } } <div class="text-center"> <p>@result</p> </div>
運行效果(環境: win10 + asp.netcore5.0 + vscode):ui
我在網上找了一個 https://github.com/928799934/googleAuthenticator.git直接拿來用了以西, 能夠的,首先二維碼 otpauth://totp/gitlab.com:410534805@qq.com?secret=LC42VPXL3VUMBCAN&issuer=gitlab.com 咱們能夠用草料來生成 , 而後用手機來掃碼,而後在調用API來驗證:this
package main import ( "fmt" "gotest/googleAuthenticator" ) func createSecret(ga *googleAuthenticator.GAuth) string { secret, err := ga.CreateSecret(16) if err != nil { return "" } return secret } func getCode(ga *googleAuthenticator.GAuth, secret string) string { code, err := ga.GetCode(secret) if err != nil { return "*" } return code } func verifyCode(ga *googleAuthenticator.GAuth, secret, code string) bool { // 1:30sec ret, err := ga.VerifyCode(secret, code, 1) if err != nil { return false } return ret } func main() { /* if len(os.Args) != 2 { return } */ // 用草料二維碼 https://cli.im/ 生成 如下地址: //otpauth://totp/gitlab.com:410534805@qq.com?secret=LC42VPXL3VUMBCAN&issuer=gitlab.com secret := "LC42VPXL3VUMBCAN" //secret := os.Args[1] //secret := "IU7B5Q3VBL55Q645" ga := googleAuthenticator.NewGAuth() //code := getCode(ga, secret) code := "027093" ret := verifyCode(ga, secret, code) fmt.Println(ret) }
最後運行經過
下載地址: