totp_algorithm.dart 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import 'dart:math' show pow;
  2. import 'dart:typed_data' show Uint8List, Endian;
  3. import 'package:crypto/crypto.dart' show Hash, Hmac, sha1, sha256, sha512;
  4. import 'base32.dart' show Base32;
  5. /// Hash algorithm to OTP
  6. enum OtpHashAlgorithm {
  7. sha1,
  8. sha256,
  9. sha512;
  10. static OtpHashAlgorithm fromString(String str) {
  11. if (str == "sha1") {
  12. return OtpHashAlgorithm.sha1;
  13. } else if (str == "sha256") {
  14. return OtpHashAlgorithm.sha256;
  15. } else if (str == "sha512") {
  16. return OtpHashAlgorithm.sha512;
  17. }
  18. throw Exception("Unknown algorithm");
  19. }
  20. }
  21. extension _StringOperations on OtpHashAlgorithm {
  22. Hash toHashFunction() {
  23. if (this == OtpHashAlgorithm.sha1) {
  24. return sha1;
  25. } else if (this == OtpHashAlgorithm.sha256) {
  26. return sha256;
  27. } else if (this == OtpHashAlgorithm.sha512) {
  28. return sha512;
  29. }
  30. throw Exception("Unknown algorithm");
  31. }
  32. }
  33. /// Static class for generating TOTP codes.
  34. ///
  35. /// References:
  36. /// * RFC 4226, 6238
  37. /// * https://github.com/LanceGin/dotp/blob/master/lib/src/otp.dart
  38. /// * https://stackoverflow.com/questions/49398437
  39. class Totp {
  40. /// Formats a generated [code] to make it look nice
  41. static String prettyValue(String code) {
  42. // Length at which to split at
  43. int splitLength = code.length == 8 ? 4 : 3;
  44. // Combine 2 halves
  45. return '${code.substring(0, splitLength)} ${code.substring(splitLength)}';
  46. }
  47. /// Generates a TOTP value for the given attributes.
  48. ///
  49. /// Note:
  50. /// * [secret] should be a base32 string.
  51. /// * Currently only supports sha1 and sha256.
  52. static String generateCode(int time, String secret,
  53. [int digits = 6,
  54. int period = 30,
  55. OtpHashAlgorithm algorithm = OtpHashAlgorithm.sha1]) {
  56. // Calculate number of time steps between T0 (assumed to be Unix Epoch)
  57. // and current time
  58. int timeCounter = ((time - 0) / period).floor();
  59. // TOTP = HOTP(K, T)
  60. return _generateHOTP(secret, timeCounter, digits, algorithm);
  61. }
  62. /// Generate multiple TOTP values for the given [times].
  63. ///
  64. /// Currently only supports sha1 and sha256.
  65. static List<String> generateCodes(List<int> times, String secret,
  66. [int digits = 6,
  67. int period = 30,
  68. OtpHashAlgorithm algorithm = OtpHashAlgorithm.sha1]) {
  69. return times
  70. .map((time) => generateCode(time, secret, digits, period, algorithm))
  71. .toList();
  72. }
  73. /// Generates the HOTP code.
  74. static String _generateHOTP(
  75. String secret, int timeCounter, int digits, OtpHashAlgorithm algorithm) {
  76. var key = Base32.decode(secret);
  77. var bytes = Uint8List(8)
  78. ..buffer.asByteData().setInt64(0, timeCounter, Endian.big);
  79. // Determine algorithm
  80. var hmac = Hmac(algorithm.toHashFunction(), key);
  81. var digest = hmac.convert(bytes);
  82. int offset = digest.bytes[digest.bytes.length - 1] & 0xf;
  83. int binary = ((digest.bytes[offset] & 0x7f) << 24) |
  84. ((digest.bytes[offset + 1] & 0xff) << 16) |
  85. ((digest.bytes[offset + 2] & 0xff) << 8) |
  86. (digest.bytes[offset + 3] & 0xff);
  87. num otp = binary % (pow(10, digits));
  88. return otp.toString().padLeft(digits, "0");
  89. }
  90. }