Google Authenticator(谷歌身份驗證器)

Google Authenticator(谷歌身份驗證器)

什麼是認證器?怎麼對接?

Google Authenticator(谷歌身份驗證器)是谷歌推出的一個動態密令工具,它有兩種密令模式。分別是“TOTP 基於時間”、“HOTP 基於計數器”,通過手機上 簡單的設置就可以設定自己獨一的動態密令, 那麼我們怎麼將我們的程序和認證器進行對接呢?其實谷歌認證器並不是需要我們對接這個工具的API而是通過算法來決定,谷歌使用使用HMAC算法生成密令,通過基於次數或者基於時間兩個模板進行計算,因此在程序中只需要使用相同的算法即可與之匹配。

TOTP 基於時間

  • HMAC算法使用固定爲HmacSHA1
  • 更新時長固定爲30秒
  • APP端輸入數據維度只有兩個:賬戶名稱(自己隨意填寫方便自己查看)和base32格式的key

HOTP 基於計數器

基於計數器模式是根據一個共享祕鑰K和一個C計數器進行算法計算

認證器安裝

手機需要安裝認證器:

1、Base32.cs

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");
        }
    }
}

2、GoogleAuthenticator.cs

using GoogleAuthorization;
using System;
using System.Security.Cryptography;
using System.Text;
 
namespace GoogleAuthenticator
{
    public class GoogleAuthenticator
    {
        /// <summary>
        /// 初始化驗證碼生成規則
        /// </summary>
        /// <param name="key">祕鑰(手機使用Base32碼)</param>
        /// <param name="duration">驗證碼間隔多久刷新一次(默認30秒和google同步)</param>
        public GoogleAuthenticator(string key)
        {
            this.SERECT_KEY = key;
            this.SERECT_KEY_MOBILE = Base32.ToString(Encoding.UTF8.GetBytes(key));
            this.DURATION_TIME = 30;
        }
        /// <summary>
        /// 初始化驗證碼生成規則
        /// </summary>
        /// <param name="key">祕鑰(手機使用Base32碼)</param>
        /// <param name="duration">驗證碼間隔多久刷新一次(默認30秒和google同步)</param>
        public GoogleAuthenticator(long duration = 30, string key = "test")
        {
            this.SERECT_KEY = key;
            this.SERECT_KEY_MOBILE = Base32.ToString(Encoding.UTF8.GetBytes(key));
            this.DURATION_TIME = duration;
        }
        /// <summary>
        /// 間隔時間
        /// </summary>
        private long DURATION_TIME { get; set; }
 
        /// <summary>
        /// 迭代次數
        /// </summary>
        private long COUNTER
        {
            get
            {
                return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).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)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).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 = 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));
        }
    }
}

 

3、使用方法

 string user_Code = Request["Google_Code"].ToString();
                string google_key = Common.PDDCommon.GetCode_GoogleAuthenticator(phone);
                if (string.IsNullOrEmpty(googleCode) || google_key.ToUpper() != googleCode.ToUpper())
                {
                    json = SerializerJson.SuccessFlagToJson(0, "谷歌驗證碼不正確!");
                    Response.Clear();
                    Response.Write(json);
                    Response.End();
                }

案例地址:

git:https://github.com/CN-Yi/GoogleAuthenticator

gitee:https://gitee.com/hsyi/GoogleAuthenticator

發佈了3 篇原創文章 · 獲贊 6 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章