Introduction
Two-factor authentication (2FA) is becoming more and more important, as its adoption is driven by a need for major software companies to secure their systems against threats, as well as due to legal requirements of strong customer authentication, such as the PSD2 directive that came in force in Europe last month.
2FA can be implemented in a number of ways. Typically, it is a combination of the usual username/password login as well as something else, often being a one-time password (OTP) that is sent via SMS or email, or generated by an algorithm.
In this article, we’ll focus entirely on generating and verifying Time-Based One-Time Passwords (TOTP) using Google Authenticator and the Otp.NET library.
Update 20th October 2019: This also works if you use Microsoft Authenticator instead of Google Authenticator. Microsoft Authenticator requires more permissions on your device, sends usage data to Microsoft by default, and is slightly more confusing because you have to choose the type of account.
Update 22nd October 2019: I discovered another mobile app called Authy, and it works just as well to acquire the TOTP secret and generate codes. It is interesting because it has a mechanism to take encrypted backups in the cloud and synchronise across devices, addressing the problem of when you lose or change your phone.
About TOTP
TOTP is an algorithm used to generate one-time passwords based on a shared secret and the current time. It is defined in RFC6238, and is a variant of the HOTP algorithm (RFC4226) which uses a counter instead of time.
TOTP can be thought of as a function that takes the shared secret and current time as inputs, and generates a one-time password as output. Given that the client and server both know the same shared secret, and that their software clocks are more or less in sync without major clock skew, then they would generate the same code. This allows a code generated on a mobile device to be verified on the server side.
Generating a Shared Secret
We will use Otp.NET to perform most operations related to TOTP generation and verification. This can easily be installed in a .NET (Core) console application via NuGet:
Install-Package Otp.NET
It is then really easy to generate and output a shared secret using the following code:
var secret = KeyGeneration.GenerateRandomKey(20);
var base32Secret = Base32Encoding.ToString(secret);
Console.WriteLine(base32Secret);
The secret that we generated on the first line is an array of bytes. However, we output it in base32 encoding. This is important for the next step when we will pass the secret to the mobile device. As I learned the hard way, it does not work if the secret is an arbitrary string and not base32-encoded.
Running the above, I just got the following in the output:
6L4OH6DDC4PLNQBA5422GM67KXRDIQQP
Generating a QR Code for the Secret
Stefan Sundin made this great 2FA QR code generator. The two required fields are the Secret (where we paste the value generated above) and a Label (which is arbitrary and identifies the application — we’ll simply put “MFA Test 1” in there).
Setting up Google Authenticator
Find Google Authenticator in your phone’s app store and install it. It requires access to your camera as we’ll see in a second.
After installation and its brief in-built tutorial, you get to the point where you can set up your first TOTP code generator (they call it an “account”):
This step is where you enter the shared secret into Google Authenticator. You can do that by scanning a QR code (first option), or by typing it in (second option). The latter is slow and painful, especially on a mobile device, and should be kept as a fallback in case there is some kind of problem scanning the QR code. Scanning the QR code is really just a convenience mechanism and is an encoded version of the same secret.
Once you’ve scanned the QR code, Google Authenticator has acquired the shared secret and starts generating TOTP codes every 30 seconds:
Since you can have more than one of these code generators in here (for different applications), they come with a label. In this case, you’ll notice that we have “MFA Test 1”, which is exactly what we entered in the Label field when generating the QR code.
Generating TOTP codes from Otp.NET
If you need to generate TOTP codes from .NET code (essentially to do what Google Authenticator is doing), then Otp.NET makes it very easy to do that:
string base32Secret = "6L4OH6DDC4PLNQBA5422GM67KXRDIQQP";
var secret = Base32Encoding.ToBytes(base32Secret);
var totp = new Totp(secret);
var code = totp.ComputeTotp();
Console.WriteLine(code);
The ComputeTotp()
method takes an optional DateTime
parameter as the current time to use for the code generation algorithm. If not provided, it uses DateTime.UtcNow
, which is typically what you want to use.
Since we are using Google Authenticator, we don’t actually need this at all, so this is just something to keep in mind if you ever actually need it. It also gives some assurance that we’re on the right track, because what we’re doing in C# and on the mobile device are evidently well in sync.
Verifying TOTP Codes
Like every other operation we’ve seen, verifying TOTP codes with Otp.NET is also very easy. The following code shows how to do this, although most of the code is actually handling input and output.
string base32Secret = "6L4OH6DDC4PLNQBA5422GM67KXRDIQQP";
var secret = Base32Encoding.ToBytes(base32Secret);
var totp = new Totp(secret);
while (true)
{
Console.Write("Enter code: ");
string inputCode = Console.ReadLine();
bool valid = totp.VerifyTotp(inputCode, out long timeStepMatched,
VerificationWindow.RfcSpecifiedNetworkDelay);
string validStr = valid ? "Valid" : "Invalid";
var colour = valid ? ConsoleColor.Green : ConsoleColor.Red;
Console.ForegroundColor = colour;
Console.WriteLine(validStr);
Console.ResetColor();
}
Here’s what it might look like while you test it out repeatedly:
As you can see above, I did a number of things:
- I entered two invalid codes, and got invalid responses.
- I entered a valid code, and got a valid response as expected.
- I waited for a new code to be generated, then entered the same code as before, and it was accepted.
- I entered the new code that was generated, and it was validated.
- I entered another invalid code, and it was marked as such.
The most interesting part of the above is the third step, and it requires further explanation. Codes are generated in time windows, by default every 30 seconds. That doesn’t necessarily mean that the previous code should be rejected. The time window might have shifted just as the user was typing the code, or there could be network delays, etc. Typically, some leeway is allowed when validating these codes. The RFC recommends allowing codes from one time window in the past or future, and that’s what the value of VerificationWindow.RfcSpecifiedNetworkDelay
that we passed in as the third parameter to VerifyTotp()
does. If you want, you can pass in something different that is more lenient or more restrictive.
On the other hand, accepting the same code twice is wrong, considering we are supposed to be generating one time passwords. In order to make sure that a code isn’t used twice, we need to store something that we can later check to know whether a code has been used. That’s the reason for the second parameter to VerifyTotp()
. It gives us back a number indicating the time step used, so we can save this whenever a code is used, and later check whether the same time step has already been used before.
Assuming a single shared secret, a very quick-and-dirty dummy implementation using a HashSet
instead of real persistence could look something like this:
string base32Secret = "6L4OH6DDC4PLNQBA5422GM67KXRDIQQP";
var secret = Base32Encoding.ToBytes(base32Secret);
var totp = new Totp(secret);
var usedTimeSteps = new HashSet<long>();
while (true)
{
Console.Write("Enter code: ");
string inputCode = Console.ReadLine();
bool valid = totp.VerifyTotp(inputCode, out long timeStepMatched,
VerificationWindow.RfcSpecifiedNetworkDelay);
valid &= !usedTimeSteps.Contains(timeStepMatched);
usedTimeSteps.Add(timeStepMatched);
string validStr = valid ? "Valid" : "Invalid";
var colour = valid ? ConsoleColor.Green : ConsoleColor.Red;
Console.ForegroundColor = colour;
Console.WriteLine(validStr);
Console.ResetColor();
}
Like this, there’s no way you can ever have the same code be valid twice:
Conclusion
In this article we’ve seen how Time-Based One-Time Passwords can be generated and verified. We’ve focused mainly on:
- Generating a shared secret using Otp.NET
- Bringing it to a mobile device with Google Authenticator
- Using Google Authenticator to generate TOTP codes
- Using Otp.NET to validate these codes
In a two-factor authentication implementation, this is of course only one of the factors, and usually takes place after a regular username/password login.
Thanks a ton. This was very helpful.