Is there a tutorial on how to implement Google Authenticator in .NET apps? [closed]
Asked Answered
G

6

61

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.

Gonophore answered 21/6, 2011 at 7:44 Comment(1)
According to our on-topic guidance, "Some questions are still off-topic, even if they fit into one of the categories listed above:...Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic..."Lippi
G
17

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

Gonophore answered 21/6, 2011 at 12:48 Comment(0)
N
51

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
}
Nissensohn answered 13/9, 2012 at 1:44 Comment(9)
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.Gonophore
@Michael Petito, do you have this on GitHub?Patina
@lorddev, No, I haven't put this up on GitHub.Nissensohn
I've made a Gist for this on GitHub: gist.github.com/KennethRMason/4996944Coagulate
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.Hungary
@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.Nissensohn
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.Hungary
@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.Nissensohn
@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)."Hungary
N
29

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";
}
Northeastward answered 16/6, 2017 at 9:13 Comment(4)
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.Voyles
Very easy to implementAvirulent
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.Ehtelehud
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 learn.microsoft.com/en-us/dotnet/core/compatibility/…Arezzo
G
17

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

Gonophore answered 21/6, 2011 at 12:48 Comment(0)
O
7

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"

Omland answered 8/11, 2013 at 12:53 Comment(0)
S
3

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!");
            }
        }
}
Seaden answered 16/1, 2019 at 12:31 Comment(4)
I tried the exact solution as in your example, and the code in Google Authenticator app is different...Incubator
@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.Seaden
Thanks, the problem in my case was that my system had incorrect time and because of that it generated different codes.Incubator
@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.Seaden
O
2

I didn't find a tutorial, but it seems like writing a port wouldn't be so hard. It is a Java application, and is based off existing standards (HMAC SHA1).

See this page for details on the non-GUI guts:

And see these pages for info on porting, and an existing (unofficial) Silverlight port:

Ola answered 21/6, 2011 at 8:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.