Skip to content
/ EzOTP Public

A simple, easy-to-use one-time password generator implementation for .NET Core 3.1. Compatible with Google Authenticator.

License

Notifications You must be signed in to change notification settings

Emzi0767/EzOTP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EzOTP

A simple, lightweight, and easy to use implementation of One-Time Password protocol, both time-based and counter-based variants.

Installation

To start using the package, just install it from NuGet.

Usage

The library is designed to be fairly straightforward and easy to use.

Basic usage

The primary use case for the library is usage from QR codes, or other media containing OTP authentication URIs (that is, URIs with otpauth scheme).

var otp = OtpGenerator.ParseUri("otpauth://totp/ACME%20Co:john@example.com?secret=DGW24UIKQZBELXEMY64PICAL5IGYMJM6&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30");
var code = otp.GenerateCode();

This will generate a code in XXXXXX format, as a string.

Generate raw code

In the event a numeric code is more desired (e.g. for comparison purposes), one can use GenerateRaw() method instead. It returns an integer with appropriate number of digits.

var otp = OtpGenerator.ParseUri("otpauth://totp/ACME%20Co:john@example.com?secret=DGW24UIKQZBELXEMY64PICAL5IGYMJM6&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30");
var code = otp.GenerateRaw();

Groupping digits

groupSize argument of GenerateCode() defines a maximum number of digits in a single group. For example, if the generator is configured for generating 6 digit-long codes, the output looks like so, depending on the value of groupSize argument:

  • 1: X X X X X X
  • 2: XX XX XX
  • 3: XXX XXX
  • 4: XX XXXX
  • 5: X XXXXX
  • 6+: XXXXXX

Handling desynchronization

Desynchronization between 2 generators can occur. Because of this, EzOTP provides 2 ways of handling potentially-desynchronized generators.

Method 1: Offset

If the desynchronization offset of both counters is known, one can use offset argument of GenerateCode() method. This will add a fixed offset to generated counter values, without changing the counter value itself.

For example, using Google Authenticator-compatible examble from Basic usage section, let's assume the desynchronization between the two devices is between 60 and 90 seconds, such that current application is lagging.

Since Google Authenticator uses periods of 30 seconds, this means we have to add 60 / 30 = 2 periods to the counter, meaning we need to use an offset of 2:

var otp = OtpGenerator.ParseUri("otpauth://totp/ACME%20Co:john@example.com?secret=DGW24UIKQZBELXEMY64PICAL5IGYMJM6&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30");
var code = otp.GenerateCode(offset: 2);

Should the current application be the one ahead, an offset of -2 would be applied instead.

Method 2: Counter window

For authenticating, RFC actually recommends testing several codes in the past and future. However generating several codes using above methods is not the best choice, as it does trip the counter internally, meaning that if the generator type uses a counter (that is, is a HOTP generator), it will increase the internal counter value for every code generated.

To alleviate this issue, EzOTP provides methods for generating a "window" of codes, whilst incrementing the counter only once.

Assuming that user-provided code is in an int variable called userCode, one can validate their input like so:

var otp = OtpGenerator.ParseUri("otpauth://totp/ACME%20Co:john@example.com?secret=DGW24UIKQZBELXEMY64PICAL5IGYMJM6&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30");
if (!otp.GenerateRawWindow(window: 1).Contains(userCode))
    // fail the authentication process

The method optionally takes a window argument, which defines the window size. Given a window size of n, EzOTP will generate n codes before current counter value, 1 current counter value code, and n codes after current counter value.

Specifying a value of 0 or a negative number will use default window sizes. For TOTP it's 1, for HOTP it's 2.

OTP generator configuration

An OTP generator can be constructed without parsing an URI, by constructing an instance of OtpGeneratorConfiguration directly. It's an abstract class, which serves as a common base for 2 configuration types:

  • TotpGeneratorConfiguration: Contains TOTP-specific properties.
  • HotpGeneratorConfiguration: Contains HOTP-specific properties.

The configuration instance can then be supplied to the constructor of OtpGenerator. This allows for storing OTP generator state and restoring it without having to use URIs. This is useful for HOTP particularly, where the counter value has to be re-recorded after every generation.

Generating Google Authenticator URIs

One can easily generate configurations for Google Authenticator, by using GenerateGoogleAuthenticator() static method on either TotpGeneratorConfiguration or HotpGeneratorConfiguration classes.

These methods return configuration objects, that can then be plugged into OtpGenerator constructor, or just returned as an URI to a user.

Support me

Lots of effort went into making this software.

If you feel like I'm doing a good job, or just want to throw money at me, you can do so through any of the following:

Other questions

If you have other questions or would like to talk in general, feel free to visit my Discord server.

Emzi's Central Dispatch