daegalus / dart-otp

RFC6238 Time-Based One-Time Password / Google Authenticator Library
MIT License
100 stars 25 forks source link

TOTP generates invalid codes #42

Closed Davenchy closed 1 year ago

Davenchy commented 2 years ago

The packages generates invalid codes I used this site https://totp.danhersam.com to validate the codes but the site generated code and the package code are not the same. Also I tried this pub package https://pub.dev/packages/dart_dash_otp and it works will. So if you can fix your package it will be great because it toke a long time to find another working solution as your package is suggested first.

daegalus commented 1 year ago

Can you provide me more info on how you generate a code? There are a couple options that can affect this. The biggest one is generating a RFC4226 strict otp or a GOogle Authenticator otp code. Google uses non-standard options by default, hence why I have the isGoogle option.

Davenchy commented 1 year ago

You can use this site https://totp.danhersam.com/ just copied all the provided options and the output is not the same The website gives the same output of real world apps e.g. authy when I am using my real secret e.g. github totp secret the site generates the same code I used another dart package called dart_dash_otp and it generates the same correct code too.

daegalus commented 1 year ago

Well my library doesn't do a lot of hand-holding, so you need to take certain things into account, like TimeZone, if its a Google mode authenticator or an RFC6238 authenticator. Google doesn't follow the official RFC spec, and unlike many libraries, I do not default to their standard, but I do support it.

That is why I asked for your code to see what you were doing, not where you were checking it against. Because there are a few nobs that usually need adjusting.

So things you need to do:

The following code gives me the same code as that site you gave me, adjust for your timezone:

import 'package:otp/otp.dart';
import 'package:timezone/data/latest.dart' as timezone;
import 'package:timezone/timezone.dart' as timezone;

void main() {
  final now = DateTime.now();
  timezone.initializeTimeZones();

  final pacificTimeZone = timezone.getLocation('America/Los_Angeles');
  final date = timezone.TZDateTime.from(now, pacificTimeZone);

  final code = OTP.generateTOTPCodeString(
      'JBSWY3DPEHPK3PXP', date.millisecondsSinceEpoch,
      algorithm: Algorithm.SHA1, isGoogle: true);
  print(code);
}
Davenchy commented 1 year ago

OK Bro Thanks, I tried with isGoogle set to true and it worked. Really sorry for the misunderstand.

jaxnz commented 1 year ago

Well my library doesn't do a lot of hand-holding, so you need to take certain things into account, like TimeZone, if its a Google mode authenticator or an RFC6238 authenticator. Google doesn't follow the official RFC spec, and unlike many libraries, I do not default to their standard, but I do support it.

That is why I asked for your code to see what you were doing, not where you were checking it against. Because there are a few nobs that usually need adjusting.

So things you need to do:

  • Make sure your time is adjusted for your timezone, by default DateTime.now() for Dart is UTC. Javascript (at least in the browser) defaults to your timezone.
  • Make sure you are using SHA1 algorithm. The default is SHA256 in modern TOTP, but google is still on SHA1, but does support SHA256, but most apps don't use it.
  • You have to pass in isGoogle: true so that the secret is parsed through Base32, thats a google, thing. RFC variant doesn't base32 decode, it uses the string directly.

The following code gives me the same code as that site you gave me, adjust for your timezone:

import 'package:otp/otp.dart';
import 'package:timezone/data/latest.dart' as timezone;
import 'package:timezone/timezone.dart' as timezone;

void main() {
  final now = DateTime.now();
  timezone.initializeTimeZones();

  final pacificTimeZone = timezone.getLocation('America/Los_Angeles');
  final date = timezone.TZDateTime.from(now, pacificTimeZone);

  final code = OTP.generateTOTPCodeString(
      'JBSWY3DPEHPK3PXP', date.millisecondsSinceEpoch,
      algorithm: Algorithm.SHA1, isGoogle: true);
  print(code);
}

Thank you for this, we required these settings to complete unit tests, maybe add this to the documentation? Was good to mention that isGoogle does the base32 decode too.