Есть ли руководство по внедрению Google Authenticator в приложениях .NET? - PullRequest
40 голосов
/ 21 июня 2011

Я ищу учебник о том, как использовать Google Authenticator в .NET-приложениях.Существует ли это, и если да, где я могу его найти?

Я понимаю, что это можно использовать для добавления двухфакторной аутентификации в ваши собственные приложения.

Ответы [ 6 ]

40 голосов
/ 13 сентября 2012

Играя с Google Authenticator, я столкнулся с этим вопросом и, в частности, с кодом, предоставленным Espo. Лично меня не удовлетворил переход с Java на C #, и я решил поделиться своей версией. Помимо интенсивного рефакторинга кода:

  • Введена проверка порядка байтов с прямым порядком байтов и при необходимости конвертирована в последовательность с прямым порядком байтов.
  • Введен параметр для ключа HMAC.

Дополнительные сведения о формате URL-адреса предоставления см. Также: https://github.com/google/google-authenticator/wiki/Key-Uri-Format

Не стесняйтесь использовать, если хотите, и спасибо Espo за начальную работу.

using System;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
using System.Text;

public class GoogleAuthenticator
{
    const int IntervalLength = 30;
    const int PinLength = 6;
    static readonly int PinModulo = (int)Math.Pow(10, PinLength);
    static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    /// <summary>
    ///   Number of intervals that have elapsed.
    /// </summary>
    static long CurrentInterval
    {
        get
        {
            var ElapsedSeconds = (long)Math.Floor((DateTime.UtcNow - UnixEpoch).TotalSeconds);

            return ElapsedSeconds/IntervalLength;
        }
    }

    /// <summary>
    ///   Generates a QR code bitmap for provisioning.
    /// </summary>
    public byte[] GenerateProvisioningImage(string identifier, byte[] key, int width, int height)
    {
        var KeyString = Encoder.Base32Encode(key);
        var ProvisionUrl = Encoder.UrlEncode(string.Format("otpauth://totp/{0}?secret={1}&issuer=MyCompany", identifier, KeyString));

        var ChartUrl = string.Format("https://chart.apis.google.com/chart?cht=qr&chs={0}x{1}&chl={2}", width, height, ProvisionUrl);
        using (var Client = new WebClient())
        {
            return Client.DownloadData(ChartUrl);
        }
    }

    /// <summary>
    ///   Generates a pin for the given key.
    /// </summary>
    public string GeneratePin(byte[] key)
    {
        return GeneratePin(key, CurrentInterval);
    }

    /// <summary>
    ///   Generates a pin by hashing a key and counter.
    /// </summary>
    static string GeneratePin(byte[] key, long counter)
    {
        const int SizeOfInt32 = 4;

        var CounterBytes = BitConverter.GetBytes(counter);

        if (BitConverter.IsLittleEndian)
        {
            //spec requires bytes in big-endian order
            Array.Reverse(CounterBytes);
        }

        var Hash = new HMACSHA1(key).ComputeHash(CounterBytes);
        var Offset = Hash[Hash.Length - 1] & 0xF;

        var SelectedBytes = new byte[SizeOfInt32];
        Buffer.BlockCopy(Hash, Offset, SelectedBytes, 0, SizeOfInt32);

        if (BitConverter.IsLittleEndian)
        {
            //spec interprets bytes in big-endian order
            Array.Reverse(SelectedBytes);
        }

        var SelectedInteger = BitConverter.ToInt32(SelectedBytes, 0);

        //remove the most significant bit for interoperability per spec
        var TruncatedHash = SelectedInteger & 0x7FFFFFFF;

        //generate number of digits for given pin length
        var Pin = TruncatedHash%PinModulo;

        return Pin.ToString(CultureInfo.InvariantCulture).PadLeft(PinLength, '0');
    }

    #region Nested type: Encoder

    static class Encoder
    {
        /// <summary>
        ///   Url Encoding (with upper-case hexadecimal per OATH specification)
        /// </summary>
        public static string UrlEncode(string value)
        {
            const string UrlEncodeAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";

            var Builder = new StringBuilder();

            for (var i = 0; i < value.Length; i++)
            {
                var Symbol = value[i];

                if (UrlEncodeAlphabet.IndexOf(Symbol) != -1)
                {
                    Builder.Append(Symbol);
                }
                else
                {
                    Builder.Append('%');
                    Builder.Append(((int)Symbol).ToString("X2"));
                }
            }

            return Builder.ToString();
        }

        /// <summary>
        ///   Base-32 Encoding
        /// </summary>
        public static string Base32Encode(byte[] data)
        {
            const int InByteSize = 8;
            const int OutByteSize = 5;
            const string Base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

            int i = 0, index = 0;
            var Builder = new StringBuilder((data.Length + 7)*InByteSize/OutByteSize);

            while (i < data.Length)
            {
                int CurrentByte = data[i];
                int Digit;

                //Is the current digit going to span a byte boundary?
                if (index > (InByteSize - OutByteSize))
                {
                    int NextByte;

                    if ((i + 1) < data.Length)
                    {
                        NextByte = data[i + 1];
                    }
                    else
                    {
                        NextByte = 0;
                    }

                    Digit = CurrentByte & (0xFF >> index);
                    index = (index + OutByteSize)%InByteSize;
                    Digit <<= index;
                    Digit |= NextByte >> (InByteSize - index);
                    i++;
                }
                else
                {
                    Digit = (CurrentByte >> (InByteSize - (index + OutByteSize))) & 0x1F;
                    index = (index + OutByteSize)%InByteSize;

                    if (index == 0)
                    {
                        i++;
                    }
                }

                Builder.Append(Base32Alphabet[Digit]);
            }

            return Builder.ToString();
        }
    }

    #endregion
}
11 голосов
/ 16 июня 2017

Чтобы добавить двухфакторную аутентификацию Google с помощью Google Authenticator, вам необходимо:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Security.Cryptography;
using System.Text;
using System.Web.Profile;
using System.Web.Security;
using Google.Authenticator;

, чтобы получить Google.Authenticator;отметьте здесь https://www.nuget.org/packages/GoogleAuthenticator

, теперь настраивая аутентификацию Google.

TwoFactorAuthenticator tfa = new TwoFactorAuthenticator();
var setupInfo = tfa.GenerateSetupCode("Name of the app", "More info ABout the App", "SuperSecretKeyGoesHere", 300 , 300); //the width and height of the Qr Code in pixels

string qrCodeImageUrl = setupInfo.QrCodeSetupImageUrl; //  assigning the Qr code information + URL to string
string manualEntrySetupCode = setupInfo.ManualEntryKey; // show the Manual Entry Key for the users that don't have app or phone
Image1.ImageUrl = qrCodeImageUrl;// showing the qr code on the page "linking the string to image element"
Label1.Text = manualEntrySetupCode; // showing the manual Entry setup code for the users that can not use their phone

вы можете изменить SuperSecretKeyGoesHere на любое значение, которое вы хотите, но убедитесь, что оно имеет более 10 символов, в противном случаесгенерированный вручную ключ ввода не будет работать.Теперь вы можете проверить ввод пользователя с помощью текстового поля и нажатия кнопки

, этот бит будет смотреть на запись пользователя и посмотреть, все ли в порядке

string user_enter=TextBox1.Text;
TwoFactorAuthenticator tfa = new TwoFactorAuthenticator();
bool isCorrectPIN = tfa.ValidateTwoFactorPIN("SuperSecretKeyGoesHere", user_enter);
if (isCorrectPIN == true)
{
Label2.Text = "i am cool";

}
else
{

Label2.Text = "i am Fool";
}
11 голосов
/ 21 июня 2011

После небольшого исследования и тестирования я создал собственное «доказательство концепции» о том, как вы можете сгенерировать QR-изображение, отсканировать его с телефона, а затем убедиться, что пин-код на телефоне правильный. Может быть, это может быть развито как библиотека, если кто-то хочет присоединиться? Код можно найти здесь:

https://github.com/esp0/googleAuthNet

6 голосов
/ 08 ноября 2013

Вопрос, заданный для учебника, на который другие ответы, которые я не чувствую,

, можно найти по адресу:

http://www.codeproject.com/Articles/403355/Implementing-Two-Factor-Authentication-in-ASP-NET

Учебное пособие былонаписано Риком Бэссхэмом и содержит информацию о:

«Что такое двухфакторная аутентификация», «Что такое Google Authenticator», «Как это работает»

Затем объясняется, как реализовать код для:

«Генерация одноразовых паролей на основе счетчиков» «Генерация одноразовых паролей на основе времени»

И дает полное руководство по использованию Visual Studio 2010 в разделе:

«Как это сделать?использовать "

2 голосов
/ 21 июня 2011

Я не нашел учебник, но кажется, что написать порт не будет так сложно.Это Java-приложение, основанное на существующих стандартах (HMAC SHA1).

См. Эту страницу для получения подробных сведений о не-GUI кишках:

На этих страницах вы найдете информацию о портировании и существующем (неофициальном) порте Silverlight:

1 голос
/ 16 января 2019

Вы можете запустить это простое консольное приложение, чтобы понять, как проверить однократный код токена.Обратите внимание, что сначала нам нужно установить библиотеку Otp.Net из пакета Nuget.

static string secretKey = "JBSWY3DPEHPK3PXP"; //add this key to your Google Authenticator app  

private static void Main(string[] args)
{
        var bytes = Base32Encoding.ToBytes(secretKey);

        var totp = new Totp(bytes);

        while (true)
        {
            Console.Write("Enter your code from Google Authenticator app: ");
            string userCode = Console.ReadLine();

            //Generate one time token code
            string tokenInApp = totp.ComputeTotp();
            int remainingSeconds = totp.RemainingSeconds();

            if (userCode.Equals(tokenInApp)
                && remainingSeconds > 0)
            {
                Console.WriteLine("Success!");
            }
            else
            {
                Console.WriteLine("Failed. Try again!");
            }
        }
}
...