diff --git a/.gitignore b/.gitignore index dd53b36..aba7f56 100644 --- a/.gitignore +++ b/.gitignore @@ -194,3 +194,11 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt .vscode/ +.idea/.idea.Google.Authenticator/.idea/.name +.idea/.idea.Google.Authenticator/.idea/codeStyles/codeStyleConfig.xml +.idea/.idea.Google.Authenticator/.idea/encodings.xml +.idea/.idea.Google.Authenticator/.idea/indexLayout.xml +.idea/.idea.Google.Authenticator/.idea/projectSettingsUpdater.xml +.idea/.idea.Google.Authenticator/.idea/vcs.xml +.idea/.idea.Google.Authenticator/.idea/workspace.xml +.idea/config/applicationhost.config diff --git a/Google.Authenticator.Tests/AuthCodeTest.cs b/Google.Authenticator.Tests/AuthCodeTest.cs index f2a3394..87f73f3 100644 --- a/Google.Authenticator.Tests/AuthCodeTest.cs +++ b/Google.Authenticator.Tests/AuthCodeTest.cs @@ -1,6 +1,6 @@ -using System; -using Xunit; +using System.Text; using Shouldly; +using Xunit; namespace Google.Authenticator.Tests { @@ -9,17 +9,35 @@ public class AuthCodeTest [Fact] public void BasicAuthCodeTest() { - string secretKey = "PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH"; - string expected = "551508"; + var secretKey = "PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH"; + var expected = "551508"; + + var tfa = new TwoFactorAuthenticator(); - TwoFactorAuthenticator tfa = new TwoFactorAuthenticator(); - - long currentTime = 1416643820; + var currentTime = 1416643820; - // I actually think you are supposed to divide the time by 30 seconds? Maybe need an overload that takes a DateTime? + // I actually think you are supposed to divide the time by 30 seconds? + // Maybe need an overload that takes a DateTime? var actual = tfa.GeneratePINAtInterval(secretKey, currentTime, 6); - actual.ShouldBe(expected); + actual.ShouldBe(expected); + } + + [Fact] + public void Base32AuthCodeTest() + { + var secretKey = Base32Encoding.ToString(Encoding.UTF8.GetBytes("PJWUMZKAUUFQKJBAMD6VGJ6RULFVW4ZH")); + var expected = "551508"; + + var tfa = new TwoFactorAuthenticator(); + + var currentTime = 1416643820; + + // I actually think you are supposed to divide the time by 30 seconds? + // Maybe need an overload that takes a DateTime? + var actual = tfa.GeneratePINAtInterval(secretKey, currentTime, 6, true); + + actual.ShouldBe(expected); } } -} +} \ No newline at end of file diff --git a/Google.Authenticator.Tests/Google.Authenticator.Tests.csproj b/Google.Authenticator.Tests/Google.Authenticator.Tests.csproj index ee9e1b1..8ee17c7 100644 --- a/Google.Authenticator.Tests/Google.Authenticator.Tests.csproj +++ b/Google.Authenticator.Tests/Google.Authenticator.Tests.csproj @@ -1,21 +1,26 @@  - netcoreapp3.0;net452 + netcoreapp3.0;net452;net5 false - + - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - + \ No newline at end of file diff --git a/Google.Authenticator.Tests/QRCodeTest.cs b/Google.Authenticator.Tests/QRCodeTest.cs index c3bb3ae..7af20d1 100644 --- a/Google.Authenticator.Tests/QRCodeTest.cs +++ b/Google.Authenticator.Tests/QRCodeTest.cs @@ -1,4 +1,3 @@ -using System; using Xunit; using Shouldly; @@ -10,7 +9,13 @@ public class QRCodeTest public void CanGenerateQRCode() { var subject = new TwoFactorAuthenticator(); - var setupCodeInfo = subject.GenerateSetupCode("issuer","a@b.com","secret", false, 2); + var setupCodeInfo = subject.GenerateSetupCode( + "issuer", + "a@b.com", + "secret", + false, + 2); + setupCodeInfo.QrCodeSetupImageUrl.ShouldNotBeNull(); } } diff --git a/Google.Authenticator/Base32Encoding.cs b/Google.Authenticator/Base32Encoding.cs index b9a6b23..0ea237f 100644 --- a/Google.Authenticator/Base32Encoding.cs +++ b/Google.Authenticator/Base32Encoding.cs @@ -16,41 +16,39 @@ public static byte[] ToBytes(string input) { if (string.IsNullOrEmpty(input)) { - throw new ArgumentNullException("input"); + throw new ArgumentNullException(nameof(input)); } input = input.TrimEnd('='); //remove padding characters - int byteCount = input.Length * 5 / 8; //this must be TRUNCATED - byte[] returnArray = new byte[byteCount]; + var byteCount = input.Length * 5 / 8; //this must be TRUNCATED + var returnArray = new byte[byteCount]; byte curByte = 0, bitsRemaining = 8; - int mask = 0, arrayIndex = 0; + int mask, arrayIndex = 0; - foreach (char c in input) + foreach (var c in input) { - int cValue = CharToValue(c); + var cValue = CharToValue(c); if (bitsRemaining > 5) { mask = cValue << (bitsRemaining - 5); - curByte = (byte)(curByte | mask); + curByte = (byte) (curByte | mask); bitsRemaining -= 5; } else { mask = cValue >> (5 - bitsRemaining); - curByte = (byte)(curByte | mask); + curByte = (byte) (curByte | mask); returnArray[arrayIndex++] = curByte; - curByte = (byte)(cValue << (3 + bitsRemaining)); + curByte = (byte) (cValue << (3 + bitsRemaining)); bitsRemaining += 3; } } //if we didn't end with a full byte if (arrayIndex != byteCount) - { returnArray[arrayIndex] = curByte; - } return returnArray; } @@ -64,29 +62,29 @@ public static string ToString(byte[] input) { if (input == null || input.Length == 0) { - throw new ArgumentNullException("input"); + throw new ArgumentNullException(nameof(input)); } - int charCount = (int)Math.Ceiling(input.Length / 5d) * 8; - char[] returnArray = new char[charCount]; + var charCount = (int) Math.Ceiling(input.Length / 5d) * 8; + var returnArray = new char[charCount]; byte nextChar = 0, bitsRemaining = 5; - int arrayIndex = 0; + var arrayIndex = 0; - foreach (byte b in input) + foreach (var b in input) { - nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining))); + nextChar = (byte) (nextChar | (b >> (8 - bitsRemaining))); returnArray[arrayIndex++] = ValueToChar(nextChar); if (bitsRemaining < 4) { - nextChar = (byte)((b >> (3 - bitsRemaining)) & 31); + nextChar = (byte) ((b >> (3 - bitsRemaining)) & 31); returnArray[arrayIndex++] = ValueToChar(nextChar); bitsRemaining += 5; } bitsRemaining -= 3; - nextChar = (byte)((b << bitsRemaining) & 31); + nextChar = (byte) ((b << bitsRemaining) & 31); } //if we didn't end with a full char @@ -101,41 +99,42 @@ public static string ToString(byte[] input) private static int CharToValue(char c) { - int value = (int)c; + var value = (int) c; //65-90 == uppercase letters if (value < 91 && value > 64) { return value - 65; } + //50-55 == numbers 2-7 if (value < 56 && value > 49) { return value - 24; } + //97-122 == lowercase letters if (value < 123 && value > 96) { return value - 97; } - throw new ArgumentException("Character is not a Base32 character.", "c"); + throw new ArgumentException("Character is not a Base32 character.", nameof(c)); } private static char ValueToChar(byte b) { if (b < 26) { - return (char)(b + 65); + return (char) (b + 65); } if (b < 32) { - return (char)(b + 24); + return (char) (b + 24); } - throw new ArgumentException("Byte is not a value Base32 value.", "b"); + throw new ArgumentException("Byte is not a value Base32 value.", nameof(b)); } - } } \ No newline at end of file diff --git a/Google.Authenticator/Google.Authenticator.csproj b/Google.Authenticator/Google.Authenticator.csproj index a8d3ce2..2bbf2ea 100644 --- a/Google.Authenticator/Google.Authenticator.csproj +++ b/Google.Authenticator/Google.Authenticator.csproj @@ -1,20 +1,20 @@  - netstandard2.0;net45 + netstandard2.0;net45;net5 Google Authenticator Two-Factor Google Authenticator Two-Factor Authentication Library Google Authenticator Two-Factor Authentication Library (Not officially affiliated with Google.) Brandon Potter Brandon Potter - 2.1.1 + 2.2.0 Apache-2.0 https://github.com/BrandonPotter/GoogleAuthenticator GoogleAuthenticator - + @@ -30,6 +30,10 @@ NET45;NETFULL + + NET5_0;NETCOREAPP + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb @@ -37,4 +41,4 @@ true 2.0.1.0 - \ No newline at end of file + diff --git a/Google.Authenticator/SetupCode.cs b/Google.Authenticator/SetupCode.cs index 657ccdf..7964d14 100644 --- a/Google.Authenticator/SetupCode.cs +++ b/Google.Authenticator/SetupCode.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Google.Authenticator +namespace Google.Authenticator { public class SetupCode { diff --git a/Google.Authenticator/TwoFactorAuthenticator.cs b/Google.Authenticator/TwoFactorAuthenticator.cs index a76cc7d..b0a5ac5 100644 --- a/Google.Authenticator/TwoFactorAuthenticator.cs +++ b/Google.Authenticator/TwoFactorAuthenticator.cs @@ -1,7 +1,6 @@ using QRCoder; using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -17,209 +16,237 @@ namespace Google.Authenticator /// public class TwoFactorAuthenticator { - private readonly static DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly DateTime _epoch = + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private TimeSpan DefaultClockDriftTolerance { get; set; } - public TwoFactorAuthenticator() - { - DefaultClockDriftTolerance = TimeSpan.FromMinutes(5); - } + public TwoFactorAuthenticator() => DefaultClockDriftTolerance = TimeSpan.FromMinutes(5); /// /// Generate a setup code for a Google Authenticator user to scan /// - /// Issuer ID (the name of the system, i.e. 'MyApp'), can be omitted but not recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format + /// Issuer ID (the name of the system, i.e. 'MyApp'), + /// can be omitted but not recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format + /// /// Account Title (no spaces) /// Account Secret Key /// Flag saying if accountSecretKey is in Base32 format or original secret - /// Number of pixels per QR Module (2 pixels give ~ 100x100px QRCode, should be 10 or less) + /// Number of pixels per QR Module (2 pixels give ~ 100x100px QRCode, + /// should be 10 or less) /// SetupCode object - public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, string accountSecretKey, bool secretIsBase32, int QRPixelsPerModule = 3) + public SetupCode GenerateSetupCode( + string issuer, + string accountTitleNoSpaces, + string accountSecretKey, + bool secretIsBase32, + int qrPixelsPerModule = 3) { - byte[] key = secretIsBase32 ? Base32Encoding.ToBytes(accountSecretKey) : Encoding.UTF8.GetBytes(accountSecretKey); - return GenerateSetupCode(issuer, accountTitleNoSpaces, key, QRPixelsPerModule); + var key = secretIsBase32 + ? Base32Encoding.ToBytes(accountSecretKey) + : Encoding.UTF8.GetBytes(accountSecretKey); + + return GenerateSetupCode(issuer, accountTitleNoSpaces, key, qrPixelsPerModule); } /// /// Generate a setup code for a Google Authenticator user to scan /// - /// Issuer ID (the name of the system, i.e. 'MyApp'), can be omitted but not recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format + /// Issuer ID (the name of the system, i.e. 'MyApp'), can be omitted but not + /// recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format /// Account Title (no spaces) /// Account Secret Key as byte[] - /// Number of pixels per QR Module (2 = ~120x120px QRCode, should be 10 or less) + /// Number of pixels per QR Module + /// (2 = ~120x120px QRCode, should be 10 or less) + /// /// SetupCode object - public SetupCode GenerateSetupCode(string issuer, string accountTitleNoSpaces, byte[] accountSecretKey, int QRPixelsPerModule = 3, bool generateQrCode = true) - { - if (String.IsNullOrWhiteSpace(accountTitleNoSpaces)) { throw new NullReferenceException("Account Title is null"); } - accountTitleNoSpaces = RemoveWhitespace(Uri.EscapeUriString(accountTitleNoSpaces)); - string encodedSecretKey = Base32Encoding.ToString(accountSecretKey); - string provisionUrl; - if (String.IsNullOrWhiteSpace(issuer)) + public SetupCode GenerateSetupCode( + string issuer, + string accountTitleNoSpaces, + byte[] accountSecretKey, + int qrPixelsPerModule = 3, + bool generateQrCode = true) + { + if (string.IsNullOrWhiteSpace(accountTitleNoSpaces)) { - provisionUrl = String.Format("otpauth://totp/{0}?secret={1}", accountTitleNoSpaces, encodedSecretKey.Trim('=')); + throw new NullReferenceException("Account Title is null"); } - else - { + + accountTitleNoSpaces = RemoveWhitespace(Uri.EscapeUriString(accountTitleNoSpaces)); + var encodedSecretKey = Base32Encoding.ToString(accountSecretKey); + + var provisionUrl = string.IsNullOrWhiteSpace(issuer) + ? $"otpauth://totp/{accountTitleNoSpaces}?secret={encodedSecretKey.Trim('=')}" // https://github.com/google/google-authenticator/wiki/Conflicting-Accounts - // Added additional prefix to account otpauth://totp/Company:joe_example@gmail.com for backwards compatibility - provisionUrl = String.Format("otpauth://totp/{2}:{0}?secret={1}&issuer={2}", accountTitleNoSpaces, encodedSecretKey.Trim('='), UrlEncode(issuer)); - } + // Added additional prefix to account otpauth://totp/Company:joe_example@gmail.com + // for backwards compatibility + : $"otpauth://totp/{UrlEncode(issuer)}:{accountTitleNoSpaces}?secret={encodedSecretKey.Trim('=')}&issuer={UrlEncode(issuer)}"; - string qrCodeUrl = string.Empty; - if (generateQrCode) + return new SetupCode( + accountTitleNoSpaces, + encodedSecretKey.Trim('='), + generateQrCode ? GenerateQrCodeUrl(qrPixelsPerModule, provisionUrl) : ""); + } + + private static string GenerateQrCodeUrl(int qrPixelsPerModule, string provisionUrl) + { + var qrCodeUrl = ""; + try { - try + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(provisionUrl, QRCodeGenerator.ECCLevel.Q)) + using (var qrCode = new QRCode(qrCodeData)) + using (var qrCodeImage = qrCode.GetGraphic(qrPixelsPerModule)) + using (var ms = new MemoryStream()) { - using (QRCodeGenerator qrGenerator = new QRCodeGenerator()) - using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(provisionUrl, QRCodeGenerator.ECCLevel.Q)) - using (QRCode qrCode = new QRCode(qrCodeData)) - using (Bitmap qrCodeImage = qrCode.GetGraphic(QRPixelsPerModule)) - using (MemoryStream ms = new MemoryStream()) - { - qrCodeImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png); - - qrCodeUrl = String.Format("data:image/png;base64,{0}", Convert.ToBase64String(ms.ToArray())); - } + qrCodeImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + qrCodeUrl = $"data:image/png;base64,{Convert.ToBase64String(ms.ToArray())}"; } - catch (System.TypeInitializationException e) + } + catch (TypeInitializationException e) + { + if (e.InnerException != null + && e.InnerException.GetType() == typeof(DllNotFoundException) + && e.InnerException.Message.Contains("libgdiplus")) { - if (e.InnerException != null - && e.InnerException.GetType() == typeof(System.DllNotFoundException) - && e.InnerException.Message.Contains("libgdiplus")) - { - throw new MissingDependencyException("It looks like libgdiplus has not been installed - see https://github.com/codebude/QRCoder/issues/227", e); - } + throw new MissingDependencyException( + "It looks like libgdiplus has not been installed - see" + + " https://github.com/codebude/QRCoder/issues/227", + e); } - catch (System.Runtime.InteropServices.ExternalException e) + } + catch (System.Runtime.InteropServices.ExternalException e) + { + if (e.Message.Contains("GDI+") && qrPixelsPerModule > 10) { - if (e.Message.Contains("GDI+") && QRPixelsPerModule > 10) - { - throw new QRException($"There was a problem generating a QR code. The value of {nameof(QRPixelsPerModule)} should be set to a value of 10 or less for optimal results.", e); - } + throw new QRException( + $"There was a problem generating a QR code. The value of {nameof(qrPixelsPerModule)}" + + " should be set to a value of 10 or less for optimal results.", + e); } } - return new SetupCode(accountTitleNoSpaces, encodedSecretKey.Trim('='), qrCodeUrl); + return qrCodeUrl; } - private static string RemoveWhitespace(string str) - { - return new string(str.Where(c => !Char.IsWhiteSpace(c)).ToArray()); - } + private static string RemoveWhitespace(string str) => + new string(str.Where(c => !char.IsWhiteSpace(c)).ToArray()); private string UrlEncode(string value) { - StringBuilder result = new StringBuilder(); - string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; + var result = new StringBuilder(); + var validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; - foreach (char symbol in value) + foreach (var symbol in value) { - if (validChars.IndexOf(symbol) != -1) + if (validChars.IndexOf(symbol) == -1) { - result.Append(symbol); + result.AppendFormat("%{0:X2}", (int) symbol); } else { - result.Append('%' + String.Format("{0:X2}", (int)symbol)); + result.Append(symbol); } } - return result.ToString().Replace(" ", "%20"); + return result.Replace(" ", "%20").ToString(); } - public string GeneratePINAtInterval(string accountSecretKey, long counter, int digits = 6) + public string GeneratePINAtInterval( + string accountSecretKey, + long counter, + int digits = 6, + bool secretIsBase32 = false) { - return GenerateHashedCode(accountSecretKey, counter, digits); + return GenerateHashedCode(accountSecretKey, counter, secretIsBase32, digits); } - internal string GenerateHashedCode(string secret, long iterationNumber, int digits = 6) + private string GenerateHashedCode(string secret, + long iterationNumber, + bool secretIsBase32, + int digits = 6) { - byte[] key = Encoding.UTF8.GetBytes(secret); - return GenerateHashedCode(key, iterationNumber, digits); + return GenerateHashedCode( + secretIsBase32 ? Base32Encoding.ToBytes(secret) : Encoding.UTF8.GetBytes(secret), + iterationNumber, + digits); } - internal string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6) + private string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6) { - byte[] counter = BitConverter.GetBytes(iterationNumber); + var counter = BitConverter.GetBytes(iterationNumber); if (BitConverter.IsLittleEndian) - { Array.Reverse(counter); - } - - HMACSHA1 hmac = new HMACSHA1(key); - byte[] hash = hmac.ComputeHash(counter); - - int offset = hash[hash.Length - 1] & 0xf; + var hmac = new HMACSHA1(key); + var hash = hmac.ComputeHash(counter); + var offset = hash[hash.Length - 1] & 0xf; // Convert the 4 bytes into an integer, ignoring the sign. - int binary = + var binary = ((hash[offset] & 0x7f) << 24) | (hash[offset + 1] << 16) | (hash[offset + 2] << 8) - | (hash[offset + 3]); + | hash[offset + 3]; - int password = binary % (int)Math.Pow(10, digits); + var password = binary % (int) Math.Pow(10, digits); return password.ToString(new string('0', digits)); } - private long GetCurrentCounter() - { - return GetCurrentCounter(DateTime.UtcNow, _epoch, 30); - } + private long GetCurrentCounter() => GetCurrentCounter(DateTime.UtcNow, _epoch, 30); - private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep) - { - return (long)(now - epoch).TotalSeconds / timeStep; - } + private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep) => + (long) (now - epoch).TotalSeconds / timeStep; - public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient) + public bool ValidateTwoFactorPIN( + string accountSecretKey, + string twoFactorCodeFromClient, + bool secretIsBase32 = false) { - return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance); + return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance, + secretIsBase32); } - public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance) + public bool ValidateTwoFactorPIN( + string accountSecretKey, + string twoFactorCodeFromClient, + TimeSpan timeTolerance, + bool secretIsBase32 = false) { - var codes = GetCurrentPINs(accountSecretKey, timeTolerance); - return codes.Any(c => c == twoFactorCodeFromClient); + return GetCurrentPINs(accountSecretKey, timeTolerance, secretIsBase32) + .Any(c => c == twoFactorCodeFromClient); } - public string GetCurrentPIN(string accountSecretKey) - { - return GeneratePINAtInterval(accountSecretKey, GetCurrentCounter()); - } + public string GetCurrentPIN(string accountSecretKey, bool secretIsBase32 = false) => + GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(), secretIsBase32: secretIsBase32); - public string GetCurrentPIN(string accountSecretKey, DateTime now) - { - return GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(now, _epoch, 30)); - } + public string GetCurrentPIN(string accountSecretKey, DateTime now, bool secretIsBase32 = false) => + GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(now, _epoch, 30)); - public string[] GetCurrentPINs(string accountSecretKey) - { - return GetCurrentPINs(accountSecretKey, DefaultClockDriftTolerance); - } + public string[] GetCurrentPINs(string accountSecretKey, bool secretIsBase32 = false) => + GetCurrentPINs(accountSecretKey, DefaultClockDriftTolerance, secretIsBase32); - public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance) + public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance, bool secretIsBase32 = false) { - List codes = new List(); - long iterationCounter = GetCurrentCounter(); - int iterationOffset = 0; + var codes = new List(); + var iterationCounter = GetCurrentCounter(); + var iterationOffset = 0; if (timeTolerance.TotalSeconds > 30) { iterationOffset = Convert.ToInt32(timeTolerance.TotalSeconds / 30.00); } - long iterationStart = iterationCounter - iterationOffset; - long iterationEnd = iterationCounter + iterationOffset; + var iterationStart = iterationCounter - iterationOffset; + var iterationEnd = iterationCounter + iterationOffset; - for (long counter = iterationStart; counter <= iterationEnd; counter++) + for (var counter = iterationStart; counter <= iterationEnd; counter++) { - codes.Add(GeneratePINAtInterval(accountSecretKey, counter)); + codes.Add(GeneratePINAtInterval(accountSecretKey, counter, secretIsBase32: secretIsBase32)); } return codes.ToArray(); } } -} +} \ No newline at end of file