import 'dart:math' show pow; import 'dart:typed_data' show Uint8List, Endian; import 'package:crypto/crypto.dart' show Hash, Hmac, sha1, sha256, sha512; import 'base32.dart' show Base32; /// Hash algorithm to OTP enum OtpHashAlgorithm { sha1, sha256, sha512; static OtpHashAlgorithm fromString(String str) { if (str == "sha1") { return OtpHashAlgorithm.sha1; } else if (str == "sha256") { return OtpHashAlgorithm.sha256; } else if (str == "sha512") { return OtpHashAlgorithm.sha512; } throw Exception("Unknown algorithm"); } } extension _StringOperations on OtpHashAlgorithm { Hash toHashFunction() { if (this == OtpHashAlgorithm.sha1) { return sha1; } else if (this == OtpHashAlgorithm.sha256) { return sha256; } else if (this == OtpHashAlgorithm.sha512) { return sha512; } throw Exception("Unknown algorithm"); } } /// Static class for generating TOTP codes. /// /// References: /// * RFC 4226, 6238 /// * https://github.com/LanceGin/dotp/blob/master/lib/src/otp.dart /// * https://stackoverflow.com/questions/49398437 class Totp { /// Formats a generated [code] to make it look nice static String prettyValue(String code) { // Length at which to split at int splitLength = code.length == 8 ? 4 : 3; // Combine 2 halves return '${code.substring(0, splitLength)} ${code.substring(splitLength)}'; } /// Generates a TOTP value for the given attributes. /// /// Note: /// * [secret] should be a base32 string. /// * Currently only supports sha1 and sha256. static String generateCode(int time, String secret, [int digits = 6, int period = 30, OtpHashAlgorithm algorithm = OtpHashAlgorithm.sha1]) { // Calculate number of time steps between T0 (assumed to be Unix Epoch) // and current time int timeCounter = ((time - 0) / period).floor(); // TOTP = HOTP(K, T) return _generateHOTP(secret, timeCounter, digits, algorithm); } /// Generate multiple TOTP values for the given [times]. /// /// Currently only supports sha1 and sha256. static List generateCodes(List times, String secret, [int digits = 6, int period = 30, OtpHashAlgorithm algorithm = OtpHashAlgorithm.sha1]) { return times .map((time) => generateCode(time, secret, digits, period, algorithm)) .toList(); } /// Generates the HOTP code. static String _generateHOTP( String secret, int timeCounter, int digits, OtpHashAlgorithm algorithm) { var key = Base32.decode(secret); var bytes = Uint8List(8) ..buffer.asByteData().setInt64(0, timeCounter, Endian.big); // Determine algorithm var hmac = Hmac(algorithm.toHashFunction(), key); var digest = hmac.convert(bytes); int offset = digest.bytes[digest.bytes.length - 1] & 0xf; int binary = ((digest.bytes[offset] & 0x7f) << 24) | ((digest.bytes[offset + 1] & 0xff) << 16) | ((digest.bytes[offset + 2] & 0xff) << 8) | (digest.bytes[offset + 3] & 0xff); num otp = binary % (pow(10, digits)); return otp.toString().padLeft(digits, "0"); } }