Is there a tutorial on how to implement Google Authenticator in .NET apps?

56,211

Solution 1

After a bit of researching and testing I created my own "proof-of-concept" on how to you can generate a QR-image, scan it from your phone, and then verify that the pin-code on the phone is correct. Maybe this could be developed further as a library if anyone wants to join? The code can be found here:

https://github.com/esp0/googleAuthNet

Solution 2

While playing around with Google Authenticator, I came across this question and in particular the code contributed by Espo. I personally wasn't satisfied with the conversion from Java to C# and so I thought I would share my version. Aside from heavily refactoring the code:

  • Introduced check for little-endian byte ordering and convert to big-endian as necessary.
  • Introduced parameter for the HMAC key.

For more information on the provisioning url format, see also: https://github.com/google/google-authenticator/wiki/Key-Uri-Format

Feel free to use if you like, and thanks to Espo for the initial work.

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
}

Solution 3

To Add Google Two Factor Authentication using Google Authenticator you need the following

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;

To get the Google.Authenticator; check here https://www.nuget.org/packages/GoogleAuthenticator

now setting up the Google authentication.

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

you can change the SuperSecretKeyGoesHere to any value that you want, but make sure it has more than 10 character otherwise the manual entry key that is generated will not work. Now you can check the user input with text box and button click

this bit will look at the user entry and see if its ok

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

Solution 4

The question asked for a tutorial which the other answers I don't feel cover,

one can be found at:

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

The tutorial was written by Rick Bassham and covers information on:

"What is Two Factor Authentication" "What is Google Authenticator" "How does it work"

It then explains how to implement code for:

"Counter Based One Time Password Generation" "Time Based One Time Password Generation"

And gives a full tutorial using Visual Studio 2010 under:

"How do I put it to use"

Solution 5

You could run this simple Console App to understand how to verify the one time token code. Note that we need to install library Otp.Net from Nuget package first.

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!");
            }
        }
}
Share:
56,211
Espo
Author by

Espo

ASP.NET/C#/E-Commerce Developer CV: stackoverflow.com/cv/espen

Updated on January 17, 2020

Comments

  • Espo
    Espo over 4 years

    I'm looking for a tutorial on how to use Google Authenticator in .NET-apps. Does this exist, and if so, where can I find it?

    I understand that this can be used to add two-factor-authentication to your own apps.

  • Espo
    Espo over 11 years
    Thank you for improving my code. It wasn't very pretty as you noticed, because I didn't want to spend much time just to do the POC.
  • Mathieu Le Tiec
    Mathieu Le Tiec about 11 years
    @Michael Petito, do you have this on GitHub?
  • Michael Petito
    Michael Petito about 11 years
    @lorddev, No, I haven't put this up on GitHub.
  • Ken Mason
    Ken Mason about 11 years
    I've made a Gist for this on GitHub: gist.github.com/KennethRMason/4996944
  • Nick Williams
    Nick Williams about 10 years
    Admittedly, I haven't read the the spec, which I intend do. But in order to get a quick "proof-of-concept" going, can somebody tell me what the arguments to the key and identifier parameters are? It SOUNDS like the "identifier" is the name that shows up in the Google Authenticator app next to the code. But I'm not sure how to create the key.
  • Michael Petito
    Michael Petito about 10 years
    @NickWilliams: the identifier is the name that will show up in the Google Authenticator app, while the key is an array of random bytes used as a shared secret. The Google Authenticator app uses the key to generate the PIN, while your application will use the key to verify the PIN. Use a RandomNumberGenerator from System.Security.Cryptography to generate a unique key for each user account.
  • Nick Williams
    Nick Williams about 10 years
    Thanks! Very helpful. For others' information, a quick Google search told me that the secret key can have a maximum length of 10 bytes, so that's the maximum length of that key byte array.
  • Michael Petito
    Michael Petito about 10 years
    @NickWilliams: Where did you find a limit of 10 bytes? I've used 12 bytes for the key array successfully in the past with the Google Authenticator app on Android.
  • Nick Williams
    Nick Williams about 10 years
    @MichaelPetito, I found it on the Google Authententicator site: code.google.com/p/google-authenticator/wiki/KeyUriFormat. One of the comments says, "Testing with the iPhone app I found there is a maximum secret length of 16 base32 characters (10 bytes decoded)."
  • Matthew Proctor
    Matthew Proctor about 6 years
    This is a very simple solution, and works well. There's a small typo above in the line var setupInfo = tfa.GenerateSetupCode, I've edited it to fix.
  • Shaun Vermaak
    Shaun Vermaak over 5 years
    Very easy to implement
  • Silencer
    Silencer about 5 years
    I tried the exact solution as in your example, and the code in Google Authenticator app is different...
  • Minh Nguyen
    Minh Nguyen about 5 years
    @Silencer: you need to make sure using exactly the same secret key in Google Authenticator app. If you enter it wrong it will be different.
  • Silencer
    Silencer about 5 years
    Thanks, the problem in my case was that my system had incorrect time and because of that it generated different codes.
  • Minh Nguyen
    Minh Nguyen about 5 years
    @Silencer: yeah it is using time-based method in the code so you need to make sure the time system is the same. Otherwise you can use counter-based method so it will not depend on time settings.
  • Sameera Udakumbura
    Sameera Udakumbura almost 4 years
    nuget.org/packages/GoogleAuthenticator This is not officially affiliated with Google. Isn't there a library which directly provides by Google. Because there might be security issues in external parties libraries. I searched a lot. but I couldn't find.
  • Alex from Jitbit
    Alex from Jitbit over 2 years
    Warning to .NET Core folks: this poroject has a System.Drawing dependency that has known issues on Linux and will be marked as "windows-only" in .NET 6 docs.microsoft.com/en-us/dotnet/core/compatibility/…