Two-factor authentication with Google Authenticator - manually type key instead of scanning QR code

Viewed 15565

In Google Authenticator app you can either scan a QR code or manually type a key provided by the issuer.

In the following screenshot you can see the setup of 2FA among Google Security settings, displaying how to get the TOTP by following the 2nd method.

Google 2FA settings - Google Authenticator setup

My question is: how is this key generated?

I'm trying to support 2FA with Google Authenticator for my website and I found many references and docs about how to generate the QR code, but none even mentioning the alternate method.

Edit:

To be a bit clearer, I'm supporting 2FA with Google Authenticator in a Grails 3 webapp. I already implemented the whole user flow by generating a secret key (Base32 string) for each user, providing a QR code for users to scan, and verifying the TOTP on login. I used as dependencies:

  • org.jboss.aerogear:aerogear-otp-java, aerogear OTP to conveniently verify user secret key against the TOTP from GA
  • org.grails.plugins:qrcode, qrcode Grails plugin to generate the QR code

My question is about the 2 ways to add a new entry in Google Authenticator app: 1. scan QR code (everything ok on my side) 2. manually type the account name along with an alphabetic code (in my 1st screenshot, the code is provided within Google Security Settings)

You can see an explicatory screenshot from GA for Android:

Google 2FA settings - Google Authenticator setup

How can I generate and provide such code (starting with fzee in the 1st screenshot, and named "provided key" in the 2nd one) to the user? I'm sure it's an encoding of the same data string also encoded in the QR code, but I don't know which (not simply Base32).

4 Answers

The Google Authenticator Setup QR code is generated based on a few things, one of these is the "secret key", so depending on the codebase you are using to build it into your site the "secret key" is normally generated first and that key is then used to generate the QR code.

If you look at this node.js module you can see what I am talking about

// generate base32 secret
var secret = GA.encode('base 32 encoded user secret') || GA.secret();

// get QRCode in SVG format
var qrCode = GA.qrCode('akanass', 'otp.js', secret);

https://github.com/njl07/otp.js/#qrcode-generation

Here is another example site where you can manually generate the QR code, You can provide a Label, User, Key and URL, and that will then generate the QR code.

https://dan.hersam.com/tools/gen-qr-code.html

Let me know what codebase you are trying to use to implement this into your site then I can help you track down where the secret key is generated

And View

@model _2FAGoogleAuthenticator.ViewModel.LoginModel

@{
    ViewBag.Title = "Login";
}

<h2>Login</h2>

@* Here we will add 2 form, 1 for Login and another one for 2FA token verification form *@
@if (ViewBag.Status == null || !ViewBag.Status)
{
    <!--Show login form here, Viewbag.Status is used for check is already veirfied from our database or not-->
    <div>@ViewBag.Message</div>
    <div>
        @using (Html.BeginForm())
        {
            <div class="form-group">
                <label for="Username">Username : </label>
                @Html.TextBoxFor(a => a.Username, new { @class = "form-control"})
            </div>
            <div class="form-group">
                <label for="Password">Password : </label>
                @Html.TextBoxFor(a => a.Password, new { @class="form-control", type="password"})
            </div>
            <input type="submit" value="Login" class="btn btn-default" />
        }
    </div>
}
else
{
    <!--Show 2FA verification form here-->
    <div>@ViewBag.Message</div>
    <div>
        <img src="@ViewBag.BarcodeImageUrl"/>
    </div>
    <div>
        **Manual Setup Code : @ViewBag.SetupCode**
    </div>
    <div>
        @using (Html.BeginForm("Verify2FA","Home", FormMethod.Post))
        {
            <input type="text" name="passcode" />
            <input type="submit" class="btn btn-success" /> 
        }
    </div>
}
public class HomeController : Controller
    {
        private const string key = "key"; // any 10-12 char string for use as private key in google authenticator
        public ActionResult Login()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Login(LoginModel login)
        {
            string message = "";
            bool status = false;

            //check username and password form our database here
            //for demo I am going to use Admin as Username and Password1 as Password static value
            if (login.Username == "sanket" && login.Password == "123")
            {
                status = true; // show 2FA form
                message = "2FA Verification";
                Session["Username"] = login.Username;

                //2FA Setup
                TwoFactorAuthenticator tfa = new TwoFactorAuthenticator();
                string UserUniqueKey = login.Username + key; //as Its a demo, I have done this way. But you should use any encrypted value here which will be unique value per user 
                Session["UserUniqueKey"] = UserUniqueKey;
                var setupInfo = tfa.GenerateSetupCode("Sanket Security", login.Username, UserUniqueKey, 300, 300);
                ViewBag.BarcodeImageUrl = setupInfo.QrCodeSetupImageUrl;
                ViewBag.SetupCode = setupInfo.ManualEntryKey;
            }
            else
            {
                message = "Invalid credential";
            }
            ViewBag.Message = message;
            ViewBag.Status = status;
            return View();
        }

        public ActionResult MyProfile()
        {
            if (Session["Username"] == null || Session["IsValid2FA"] == null || !(bool)Session["IsValid2FA"])
            {
                return RedirectToAction("Login");
            }
            ViewBag.Message = "Welcome " + Session["Username"].ToString();
            return View();
        }
        public ActionResult Verify2FA()
        {
            var token = Request["passcode"];
            TwoFactorAuthenticator tfa = new TwoFactorAuthenticator();
            string UserUniqueKey = Session["UserUniqueKey"].ToString();
            bool isValid = tfa.ValidateTwoFactorPIN(UserUniqueKey, token);
            if (isValid)
            {
                Session["IsValid2FA"] = true;
                return RedirectToAction("MyProfile", "Home");
                token=string.Empty;
            }
            return RedirectToAction("Login", "Home");
        }
    }
}
Related