BrandonPotter / GoogleAuthenticator

Simple, easy to use server-side two-factor authentication library for .NET that works with Google Authenticator and Authy.
Apache License 2.0
373 stars 126 forks source link

Is it possible to prove that the pin code was valid at certain time? #221

Closed nipua closed 4 months ago

nipua commented 4 months ago

Hello

I have secret key in my db, also I have exact time from logs, when method ValidateTwoFactorPIN was called. I need it for investigate some incidents, when users could give me information from their side about result of two factor validation. For example a pin '123456' and date '2022-06-06 14:34:51.797', could we check that this code was valid or invalid?

ahwm commented 4 months ago

@nipua since you closed this issue did you get it all figured out? I'm putting this here for benefit of anyone else with a similar use case.

There are some available overloads that would let you verify a PIN at a previous time.

var date = new DateTime(2022, 6, 6, 14, 34, 51, DateTimeKind.UTC);
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.UTC);
var iteration = (long)(now - epoch).TotalSeconds / 30;
var pins = tfa.GetCurrentPins(secret, iteration);

// or
var isValid = tfa.ValidateTwoFactorPIN(secret, providedCodeFromClient, iteration);
nipua commented 4 months ago

@ahwm Yes, thanks a lot, your code is more elegant, I forked, and made a local nuget with simple method

[Fact]
public void ValidateTwoFactorPINBySpecificDate_ValidPIN_ReturnsTrue()
{
    string secretKey = "4d51cf64ec";
    DateTime specificDate = DateTime.Parse("6/10/2024 6:59:23");
    string pin = "678588";

    TwoFactorAuthenticator tfa = new TwoFactorAuthenticator();

    bool actual = tfa.ValidateTwoFactorPINBySpecificDate(secretKey, pin, specificDate);

    actual.ShouldBe(true);
}

public bool ValidateTwoFactorPINBySpecificDate(string accountSecretKey, string pin, DateTime specificDate, bool secretIsBase32 = false)
{
    var codes = new List<string>();
    var iterationCounter = GetCountersBySecificDate(specificDate);

    var iterationOffset = 0;

    if (DefaultClockDriftTolerance.TotalSeconds >= timeStep)
        iterationOffset = Convert.ToInt32(DefaultClockDriftTolerance.TotalSeconds / timeStep);

    var iterationStart = iterationCounter - iterationOffset;
    var iterationEnd = iterationCounter + iterationOffset;

    for (var counter = iterationStart; counter <= iterationEnd; counter++)
        codes.Add(GeneratePINAtInterval(accountSecretKey, counter));

    return codes.Contains(pin);
}

However, I understood that doesn't have matter, because the result of this method, all time will be equals with my logs, it's just for my superior.