/// <summary> /// Takes a time step and computes a HOTP code /// </summary> /// <param name="counter">counter</param> /// <param name="mode">The hash mode to use</param> /// <returns>HOTP calculated code</returns> protected override string Compute(long counter, OtpHashMode mode) { var data = KeyUtilities.GetBigEndianBytes(counter); var otp = this.CalculateOtp(data, mode); return(Digits(otp, this.hotpSize)); }
/// <summary> /// Create a HOTP instance /// </summary> /// <param name="secretKey">The secret key to use in HOTP calculations</param> /// <param name="mode">The hash mode to use</param> /// <param name="hotpSize">The number of digits that the returning HOTP should have. The default is 6.</param> public Hotp(byte[] secretKey, OtpHashMode mode = OtpHashMode.Sha1, int hotpSize = 6) : base(secretKey, mode) { VerifyParameters(hotpSize); this.hotpSize = hotpSize; }
/// <summary> /// Create a HOTP instance /// </summary> /// <param name="key">The key to use in HOTP calculations</param> /// <param name="mode">The hash mode to use</param> /// <param name="hotpSize">The number of digits that the returning HOTP should have. The default is 6.</param> public Hotp(IKeyProvider key, OtpHashMode mode = OtpHashMode.Sha1, int hotpSize = 6) : base(key, mode) { VerifyParameters(hotpSize); this.hotpSize = hotpSize; }
public void ComputeHOTPRfc4226Test(OtpHashMode hash, long counter, string expectedOtp) { Hotp otpCalc = new Hotp(rfc4226Secret, hash, expectedOtp.Length); string otp = otpCalc.ComputeHOTP(counter); Assert.That(otp, Is.EqualTo(expectedOtp)); }
public void ComputeTOTPTest(string secret, OtpHashMode hash, long timestamp, string expectedOtp) { Totp otpCalc = new Totp(Encoding.UTF8.GetBytes(secret), 30, hash, expectedOtp.Length); DateTime time = DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime; string otp = otpCalc.ComputeTotp(time); Assert.That(otp, Is.EqualTo(expectedOtp)); }
public string GenerateOtp(string secretKey, int otpStep = OtpStep, OtpHashMode otpHashMode = OtpHashMode, int otpSize = OtpSize) { var otp = new Totp(Base32Encoding.Standard.ToBytes(secretKey), otpStep, otpHashMode, otpSize); var date = DateTime.UtcNow; var code = otp.ComputeTotp(date); return(code); }
/// <summary> /// Constrocutor for the abstract class. This is to guarantee that all implementations have a secret key /// </summary> /// <param name="secretKey"></param> /// <param name="mode">The hash mode to use</param> public Otp(IKeyProvider secretKey, OtpHashMode mode) { if (!(secretKey != null)) throw new ArgumentNullException("secretKey"); this.secretKey = secretKey; this.hashMode = mode; }
/// <summary> /// Constrocutor for the abstract class. This is to guarantee that all implementations have a secret key /// </summary> /// <param name="secretKey"></param> /// <param name="mode">The hash mode to use</param> public Otp(IKeyProvider secretKey, OtpHashMode mode) { if (!(secretKey != null)) { throw new ArgumentNullException("secretKey"); } this.secretKey = secretKey; this.hashMode = mode; }
/// <summary> /// Create a TOTP instance /// </summary> /// <param name="secretKey">The secret key to use in TOTP calculations</param> /// <param name="step">The time window step amount to use in calculating time windows. The default is 30 as recommended in the RFC</param> /// <param name="mode">The hash mode to use</param> /// <param name="totpSize">The number of digits that the returning TOTP should have. The default is 6.</param> /// <param name="timeCorrection">If required, a time correction can be specified to compensate of an out of sync local clock</param> public Totp(IKeyProvider secretKey, int step = 30, OtpHashMode mode = OtpHashMode.Sha1, int totpSize = 6, TimeCorrection timeCorrection = null) : base(secretKey, mode) { VerifyParameters(step, totpSize); _step = step; _totpSize = totpSize; // we never null check the corrected time object. Since it's readonly, we'll ensure that it isn't null here and provide neatral functionality in this case. _correctedTime = timeCorrection ?? TimeCorrection.UncorrectedInstance; }
/// <summary> /// Create a TOTP instance /// </summary> /// <param name="secretKey">The secret key to use in TOTP calculations</param> /// <param name="step">The time window step amount to use in calculating time windows. The default is 30 as recommended in the RFC</param> /// <param name="mode">The hash mode to use</param> /// <param name="totpSize">The number of digits that the returning TOTP should have. The default is 6.</param> /// <param name="timeCorrection">If required, a time correction can be specified to compensate of an out of sync local clock</param> public Totp(IKeyProvider secretKey, int step = 30, OtpHashMode mode = OtpHashMode.Sha1, int totpSize = 6, TimeCorrection timeCorrection = null) : base(secretKey, mode) { VerifyParameters(step, totpSize); this.step = step; this.totpSize = totpSize; // we never null check the corrected time object. Since it's readonly, we'll ensure that it isn't null here and provide neatral functionality in this case. this.correctedTime = timeCorrection ?? TimeCorrection.UncorrectedInstance; }
public WearAuthenticator(string secret, string icon, string issuer, string username, int period, int digits, OtpHashMode algorithm, List <string> categoryIds) { Secret = secret; Icon = icon; Issuer = issuer; Username = username; Period = period; Digits = digits; Algorithm = algorithm; CategoryIds = categoryIds; }
private static Totp TotpFromUrl(Uri url) { int digits; var collection = ParseAndValidateQueryString(url, out digits); if (!ValidateQueryStringFields(collection, UrlConstants.AlgorithmParameter, UrlConstants.DigitsParameter, UrlConstants.PeriodParameter, UrlConstants.SecretParameter)) { throw new ArgumentException("Invalid parameter in query string"); } OtpHashMode algorithm = OtpHashMode.Sha1; if (collection.AllKeys.Contains(UrlConstants.AlgorithmParameter)) { var algorithmRaw = collection[UrlConstants.AlgorithmParameter]; if (!Enum.TryParse <OtpHashMode>(algorithmRaw, true, out algorithm)) { throw new ArgumentException(string.Format("Invalid Algorithm {0}", algorithmRaw)); } } int period = 30; // the spec indicates that 30 is the default if (collection.AllKeys.Contains(UrlConstants.PeriodParameter)) { int tempPeriod; if (int.TryParse(collection[UrlConstants.PeriodParameter], out tempPeriod)) { if (tempPeriod < 1) { throw new ArgumentException(string.Format("Invalid Period {0}, must be at least 1", tempPeriod)); } else { period = tempPeriod; } } else { throw new ArgumentException(string.Format("Invalid digits {0}, must be a number", collection[UrlConstants.DigitsParameter])); } } var key = Base32Encoding.Standard.ToBytes(collection[UrlConstants.SecretParameter]); return(new Totp(key, step: period, mode: algorithm, totpSize: digits)); }
/// <summary> /// Constructor for the abstract class using a generic key provider /// </summary> /// <param name="key"></param> /// <param name="mode">The hash mode to use</param> public Otp(IKeyProvider key, OtpHashMode mode) { if (key == null) { throw new ArgumentNullException("key"); } this.secretKey = key; this.hashMode = mode; }
private static HashAlgorithm GetHashAlgorithmForMode(OtpHashMode mode) { switch (mode) { case OtpHashMode.Sha256: return new SHA256Managed(); case OtpHashMode.Sha512: return new SHA512Managed(); default: //case OtpHashMode.Sha1: return new SHA1Managed(); } }
/// <summary> /// Get a url for a TOTP key /// </summary> /// <param name="key">Plaintext key</param> /// <param name="user">The username</param> /// <param name="step">Timestep</param> /// <param name="mode">Hash mode</param> /// <param name="totpSize">Digits</param> /// <returns>URL</returns> public static string GetTotpUrl(byte[] key, string user, int step = 30, OtpHashMode mode = OtpHashMode.Sha1, int totpSize = 6) { var url = GetBaseKeyUrl(key, user, OtpType.Totp, totpSize); if (mode != OtpHashMode.Sha1) url += CreateParameter(UrlConstants.AlgorithmParameter, mode); if (step != 30) url += CreateParameter(UrlConstants.PeriodParameter, step); return url; }
/// <summary> /// Constructor for the abstract class. This is to guarantee that all implementations have a secret key /// </summary> /// <param name="secretKey"></param> /// <param name="mode">The hash mode to use</param> public Otp(byte[] secretKey, OtpHashMode mode) { if (!(secretKey != null)) throw new ArgumentNullException("secretKey"); if (!(secretKey.Length > 0)) throw new ArgumentException("secretKey empty"); // when passing a key into the constructor the caller may depend on the reference to the key remaining intact. this.secretKey = new InMemoryKey(secretKey); this.hashMode = mode; }
public Rfc6238AppendixBData(string totp, string mode, string time) { OtpHashMode hashMode; byte[] key; this.GetMode(mode, out hashMode, out key); this.modeValue = hashMode; this.rfcTestKeyValue = key; this.totpValue = totp; this.timeValue = DateTime.Parse(time); }
/// <summary> /// Helper method that calculates OTPs /// </summary> protected internal long CalculateOtp(byte[] data, OtpHashMode mode) { var hmacComputedHash = SecretKey.ComputeHmac(mode, data); // The RFC has a hard coded index 19 in this value. // This is the same thing but also accomodates SHA256 and SHA512 // hmacComputedHash[19] => hmacComputedHash[hmacComputedHash.Length - 1] var offset = hmacComputedHash[hmacComputedHash.Length - 1] & 0x0F; return((hmacComputedHash[offset] & 0x7f) << 24 | (hmacComputedHash[offset + 1] & 0xff) << 16 | (hmacComputedHash[offset + 2] & 0xff) << 8 | (hmacComputedHash[offset + 3] & 0xff) % 1000000); }
private static int LengthForMode(OtpHashMode mode) { switch (mode) { case OtpHashMode.Sha256: return 32; case OtpHashMode.Sha512: return 64; default: //case OtpHashMode.Sha1: return 20; } }
private static HashAlgorithm GetHashAlgorithmForMode(OtpHashMode mode) { switch (mode) { case OtpHashMode.Sha256: return(new SHA256Managed()); case OtpHashMode.Sha512: return(new SHA512Managed()); default: //case OtpHashMode.Sha1: return(new SHA1Managed()); } }
private static Totp TotpFromUrl(Uri url) { var collection = ParseAndValidateQueryString(url, out int digits); if (!ValidateQueryStringFields(collection, UrlConstants.AlgorithmParameter, UrlConstants.DigitsParameter, UrlConstants.PeriodParameter, UrlConstants.SecretParameter)) { throw new ArgumentException("Invalid parameter in query string"); } OtpHashMode algorithm = OtpHashMode.Sha1; if (collection.AllKeys.Contains(UrlConstants.AlgorithmParameter)) { var algorithmRaw = collection[UrlConstants.AlgorithmParameter]; if (!Enum.TryParse(algorithmRaw, true, out algorithm)) { throw new ArgumentException($"Invalid Algorithm {algorithmRaw}"); } } var period = 30; // the spec indicates that 30 is the default if (collection.AllKeys.Contains(UrlConstants.PeriodParameter)) { if (int.TryParse(collection[UrlConstants.PeriodParameter], out int tempPeriod)) { if (tempPeriod < 1) { throw new ArgumentException($"Invalid Period {tempPeriod}, must be at least 1"); } else { period = tempPeriod; } } else { throw new ArgumentException( $"Invalid digits {collection[UrlConstants.DigitsParameter]}, must be a number"); } } var key = Base32Encoder.Decode(collection[UrlConstants.SecretParameter]); return(new Totp(key, period, algorithm, digits)); }
/// <summary> /// Helper method that calculates OTPs /// </summary> protected internal long CalculateOtp(byte[] data, OtpHashMode mode) { byte[] hmacComputedHash = this.secretKey.ComputeHmac(mode, data); // The RFC has a hard coded index 19 in this value. // This is the same thing but also accomodates SHA256 and SHA512 // hmacComputedHash[19] => hmacComputedHash[hmacComputedHash.Length - 1] int offset = hmacComputedHash[hmacComputedHash.Length - 1] & 0x0F; return (hmacComputedHash[offset] & 0x7f) << 24 | (hmacComputedHash[offset + 1] & 0xff) << 16 | (hmacComputedHash[offset + 2] & 0xff) << 8 | (hmacComputedHash[offset + 3] & 0xff) % 1000000; }
private static HashAlgorithm GetHashAlgorithmForMode(OtpHashMode mode) { switch (mode) { case OtpHashMode.Sha256: return(SHA256.Create()); case OtpHashMode.Sha512: return(SHA512.Create()); default: //case OtpHashMode.Sha1: return(SHA1.Create()); } }
private static int LengthForMode(OtpHashMode mode) { switch (mode) { case OtpHashMode.Sha256: return(32); case OtpHashMode.Sha512: return(64); default: //case OtpHashMode.Sha1: return(20); } }
/// <summary> /// Constructor for the abstract class. This is to guarantee that all implementations have a secret key /// </summary> /// <param name="secretKey"></param> /// <param name="mode">The hash mode to use</param> protected BaseOtp(byte[] secretKey, OtpHashMode mode) { if (secretKey == null) { throw new ArgumentNullException(nameof(secretKey)); } if (!(secretKey.Length > 0)) { throw new ArgumentException("secretKey empty"); } // when passing a key into the constructor the caller may depend on the reference to the key remaining intact. _secretKey = new InMemoryKey(secretKey); HashMode = mode; }
/// <summary> /// Get a url for a TOTP key /// </summary> /// <param name="key">Plaintext key</param> /// <param name="user">The username</param> /// <param name="step">Timestep</param> /// <param name="mode">Hash mode</param> /// <param name="totpSize">Digits</param> /// <returns>URL</returns> public static string GetTotpUrl(byte[] key, string user, int step = 30, OtpHashMode mode = OtpHashMode.Sha1, int totpSize = 6) { var url = GetBaseKeyUrl(key, user, OtpType.Totp, totpSize); if (mode != OtpHashMode.Sha1) { url += CreateParameter(UrlConstants.AlgorithmParameter, mode); } if (step != 30) { url += CreateParameter(UrlConstants.PeriodParameter, step); } return(url); }
/// <summary> /// Constructor for the abstract class. This is to guarantee that all implementations have a secret key /// </summary> /// <param name="secretKey"></param> /// <param name="mode">The hash mode to use</param> public Otp(byte[] secretKey, OtpHashMode mode) { if (!(secretKey != null)) { throw new ArgumentNullException("secretKey"); } if (!(secretKey.Length > 0)) { throw new ArgumentException("secretKey empty"); } // when passing a key into the constructor the caller may depend on the reference to the key remaining intact. this.secretKey = new InMemoryKey(secretKey); this.hashMode = mode; }
protected override string Compute(long counter, OtpHashMode mode) { // As base.Compute(long, OtpHashMode), but doesn't call Digits(long, int) var data = BitConverter.GetBytes(counter); Array.Reverse(data); var otp = (int)CalculateOtp(data, mode); var builder = new StringBuilder(); for (var i = 0; i < NumDigits; i++) { builder.Append(Alphabet[otp % Alphabet.Length]); otp /= Alphabet.Length; } return(builder.ToString()); }
/// <summary> /// Uses the key to get an HMAC using the specified algorithm and data /// </summary> /// <param name="mode">The HMAC algorithm to use</param> /// <param name="data">The data used to compute the HMAC</param> /// <returns>HMAC of the key and data</returns> public byte[] ComputeHmac(OtpHashMode mode, byte[] data) { byte[] hashedValue = null; using (HMAC hmac = CreateHmacHash(mode)) { byte[] key = GetCopyOfKey(); try { hmac.Key = key; hashedValue = hmac.ComputeHash(data); } finally { KeyUtilities.Destroy(key); } } return(hashedValue); }
/// <summary> /// Create an HMAC object for the specified algorithm /// </summary> private static HMAC CreateHmacHash(OtpHashMode otpHashMode) { HMAC hmacAlgorithm = null; switch (otpHashMode) { case OtpHashMode.Sha256: hmacAlgorithm = new HMACSHA256(); break; case OtpHashMode.Sha512: hmacAlgorithm = new HMACSHA512(); break; default: //case OtpHashMode.Sha1: hmacAlgorithm = new HMACSHA1(); break; } return(hmacAlgorithm); }
private void GetMode(string mode, out OtpHashMode outputMode, out byte[] key) { switch (mode) { case "SHA256": outputMode = OtpHashMode.Sha256; key = JoinKeys(32).ToArray(); break; case "SHA512": outputMode = OtpHashMode.Sha512; key = JoinKeys(64).ToArray(); break; case "SHA1": outputMode = OtpHashMode.Sha1; key = JoinKeys(20).ToArray(); break; default: throw new Exception("Inavlid mode"); } }
public Verify Create(TimeSpan expireDate, EnumDefine.VerifyTypeEnum type, object model) { OtpUtility otpUtility = new OtpUtility(); RijndaelSimple rijndaelSimple = new RijndaelSimple(); Id = Common.Common.GenerateGuid(); OtpHashMode otpHashMode = OtpHashMode.Sha512; SaltKey = Common.Common.GenerateNonce(); SecretKey = otpUtility.GenerateRandomKey(otpHashMode); CreatedDateUtc = Extensions.GetCurrentDateUtc(); ExpireDate = CreatedDateUtc.Add(expireDate); Type = type; VerifyCode = otpUtility.GenerateOtp(SecretKey, (int)expireDate.TotalSeconds, otpHashMode, 8); Model = model; Status = EnumDefine.VerifyStatusEnum.New; UpdatedDateUtc = CreatedDateUtc; CreatedUid = string.Empty; UpdatedUid = string.Empty; string code = UnicodeUtility.ToHexString(rijndaelSimple.Encrypt(VerifyCode, SaltKey)); VerifyUrl = $"{ConfigSettingEnum.VerifyUrl.GetConfig()}?verify={Id}&code={code}"; return(this); }
/// <summary> /// Uses the key to get an HMAC using the specified algorithm and data /// </summary> /// <param name="mode">The HMAC algorithm to use</param> /// <param name="data">The data used to compute the HMAC</param> /// <returns>HMAC of the key and data</returns> public byte[] ComputeHmac(OtpHashMode mode, byte[] data) { byte[] hashedValue = null; using (HMAC hmac = CreateHmacHash(mode)) { byte[] key = this.GetCopyOfKey(); try { hmac.Key = key; hashedValue = hmac.ComputeHash(data); } finally { KeyUtilities.Destroy(key); } } return hashedValue; }
/// <summary> /// An abstract definition of a compute method. Takes a counter and runs it through the derived algorithm. /// </summary> /// <param name="counter">Counter or step</param> /// <param name="mode">The hash mode to use</param> /// <returns>OTP calculated code</returns> protected abstract string Compute(long counter, OtpHashMode mode);
/// <summary> /// Create an HOTP instance /// </summary> /// <param name="secretKey">The secret key to use in HOTP calculations</param> /// <param name="mode">The hash mode to use</param> public Hotp(IKeyProvider secretKey, OtpHashMode mode = OtpHashMode.Sha1) : base(secretKey, mode) { }
/// <summary> /// Uses the procedure defined in RFC 4226 section 7.5 to derive a key from the master key /// </summary> /// <param name="masterKey">The master key from which to derive a device specific key</param> /// <param name="serialNumber">A serial number that is unique to the authenticating device</param> /// <param name="mode">The hash mode to use. This will determine the resulting key lenght. The default is sha-1 (as per the RFC) which is 20 bytes</param> /// <returns>Derived key</returns> public static byte[] DeriveKeyFromMaster(IKeyProvider masterKey, int serialNumber, OtpHashMode mode = OtpHashMode.Sha1) { return(DeriveKeyFromMaster(masterKey, KeyUtilities.GetBigEndianBytes(serialNumber), mode)); }
/// <summary> /// Takes a time step and computes a TOTP code /// </summary> /// <param name="counter">time step</param> /// <param name="mode">The hash mode to use</param> /// <returns>TOTP calculated code</returns> protected override string Compute(long counter, OtpHashMode mode) { var data = KeyUtilities.GetBigEndianBytes(counter); var otp = this.CalculateOtp(data, mode); return Digits(otp, this.totpSize); }
/// <summary> /// Generates a random key in accordance with the RFC recommened length for each algorithm /// </summary> /// <param name="mode">HashMode</param> /// <returns>Key</returns> public static byte[] GenerateRandomKey(OtpHashMode mode = OtpHashMode.Sha1) { return(GenerateRandomKey(LengthForMode(mode))); }
/// <summary> /// Uses the procedure defined in RFC 4226 section 7.5 to derive a key from the master key /// </summary> /// <param name="masterKey">The master key from which to derive a device specific key</param> /// <param name="publicIdentifier">The public identifier that is unique to the authenticating device</param> /// <param name="mode">The hash mode to use. This will determine the resulting key lenght. The default is sha-1 (as per the RFC) which is 20 bytes</param> /// <returns>Derived key</returns> public static byte[] DeriveKeyFromMaster(IKeyProvider masterKey, byte[] publicIdentifier, OtpHashMode mode = OtpHashMode.Sha1) { if (masterKey == null) { throw new ArgumentNullException("masterKey"); } return(masterKey.ComputeHmac(mode, publicIdentifier)); }
/// <summary> /// Uses the procedure defined in RFC 4226 section 7.5 to derive a key from the master key /// </summary> /// <param name="masterKey">The master key from which to derive a device specific key</param> /// <param name="serialNumber">A serial number that is unique to the authenticating device</param> /// <param name="mode">The hash mode to use. This will determine the resulting key lenght. The default is sha-1 (as per the RFC) which is 20 bytes</param> /// <returns>Derived key</returns> public static byte[] DeriveKeyFromMaster(IKeyProvider masterKey, int serialNumber, OtpHashMode mode = OtpHashMode.Sha1) { return DeriveKeyFromMaster(masterKey, KeyUtilities.GetBigEndianBytes(serialNumber), mode); }
/// <summary> /// Uses the procedure defined in RFC 4226 section 7.5 to derive a key from the master key /// </summary> /// <param name="masterKey">The master key from which to derive a device specific key</param> /// <param name="publicIdentifier">The public identifier that is unique to the authenticating device</param> /// <param name="mode">The hash mode to use. This will determine the resulting key lenght. The default is sha-1 (as per the RFC) which is 20 bytes</param> /// <returns>Derived key</returns> public static byte[] DeriveKeyFromMaster(IKeyProvider masterKey, byte[] publicIdentifier, OtpHashMode mode = OtpHashMode.Sha1) { if (masterKey == null) throw new ArgumentNullException("masterKey"); return masterKey.ComputeHmac(mode, publicIdentifier); }
/// <summary> /// Generates a random key in accordance with the RFC recommened length for each algorithm /// </summary> /// <param name="mode">HashMode</param> /// <returns>Key</returns> public static byte[] GenerateRandomKey(OtpHashMode mode = OtpHashMode.Sha1) { return GenerateRandomKey(LengthForMode(mode)); }
/// <summary> /// Takes a time step and computes a TOTP code /// </summary> /// <param name="counter">time step</param> /// <param name="mode">The hash mode to use</param> /// <returns>TOTP calculated code</returns> protected override string Compute(long counter, OtpHashMode mode) => Digits(CalculateOtp(KeyUtilities.GetBigEndianBytes(counter), mode), _totpSize);
/// <summary> /// Create an HOTP instance /// </summary> /// <param name="secretKey">The secret key to use in HOTP calculations</param> /// <param name="mode">The hash mode to use</param> public Hotp(byte[] secretKey, OtpHashMode mode = OtpHashMode.Sha1) : base(secretKey, mode) { }
/// <summary> /// Create an HMAC object for the specified algorithm /// </summary> private static HMAC CreateHmacHash(OtpHashMode otpHashMode) { HMAC hmacAlgorithm = null; switch (otpHashMode) { case OtpHashMode.Sha256: hmacAlgorithm = new HMACSHA256(); break; case OtpHashMode.Sha512: hmacAlgorithm = new HMACSHA512(); break; default: //case OtpHashMode.Sha1: hmacAlgorithm = new HMACSHA1(); break; } return hmacAlgorithm; }
/// <remarks> /// This method mainly exists for unit tests. /// The RFC defines a decimal value in the test table that is an /// intermediate step to a final HOTP value /// </remarks> internal long ComputeHotpDecimal(long counter, OtpHashMode mode) { var hashData = KeyUtilities.GetBigEndianBytes(counter); return this.CalculateOtp(hashData, mode); }
/// <summary> /// Takes a counter and runs it through the HOTP algorithm. /// </summary> /// <param name="counter">Counter or step</param> /// <param name="mode">The hash mode to use</param> /// <returns>HOTP calculated code</returns> protected override string Compute(long counter, OtpHashMode mode) { var rawValue = ComputeHotpDecimal(counter, mode); return Otp.Digits(rawValue, 6); // all of the HOTP values are six digits long }