using System.Collections.Generic;
using System.Security.Cryptography;
public static void Main()
const int OtpClassStart = 201;
const int OtpClassCount = 1;
const string SecretKeyBase64 = "AQIDBAUGBwgJCgsMDQ4PEA==";
var cfg = new OtpConfigValue
SecretKeyBase64 = SecretKeyBase64
var day = DateTime.Parse("2019-12-30");
var tsFrom = day.Date.AddHours(5);
var tsTo = tsFrom.AddHours(1);
var coupons = GetOtpList(OtpClassStart, OtpClassCount, cfg, tsFrom, tsTo);
Console.WriteLine(OtpListToString(coupons, 8));
public class OtpConfigValue
public int TimePrecisionS { set; get; }
public int ValidPeriodM { set; get; }
public int OtpLength { set; get; }
public string SecretKeyBase64 { set; get; }
public byte[] SecretKey => Convert.FromBase64String(SecretKeyBase64);
public static LinkedList<string> GetOtpList(int otpClassStart, int otpClassCount,
OtpConfigValue otpConfig, DateTime begin, DateTime end)
var list = new LinkedList<string>();
using (var h = new HMACSHA256(otpConfig.SecretKey))
for (int otpClass = otpClassStart; otpClass <= otpClassStart + otpClassCount - 1; otpClass++)
var ts = new DateTime(begin.Year, begin.Month, begin.Day, begin.Hour, begin.Minute, 0);
var otpString = GetOneTimePassword(ts, (byte)otpClass, h, otpConfig.OtpLength);
ts = ts.AddSeconds(otpConfig.TimePrecisionS);
public static string GetOneTimePassword(DateTime ts, byte otpClass, HMACSHA256 hashFunc, int otpLength)
var dateNum = ts.Year << 16 | ts.Month << 8 | ts.Day;
var dateBytes = BitConverter.GetBytes(dateNum);
var timeNum = ts.Hour << 24 | ts.Minute << 16 | ts.Second << 8 | otpClass;
var timeBytes = BitConverter.GetBytes(timeNum);
if (!BitConverter.IsLittleEndian)
Array.Reverse(dateBytes);
Array.Reverse(timeBytes);
var finalBytes = new byte[dateBytes.Length + timeBytes.Length + hashFunc.Key.Length];
Array.Copy(dateBytes, 0, finalBytes, finalBytes.Length - dateBytes.Length, dateBytes.Length);
Array.Copy(timeBytes, 0, finalBytes, finalBytes.Length - dateBytes.Length - timeBytes.Length, timeBytes.Length);
Array.Copy(hashFunc.Key, 0, finalBytes, 0, hashFunc.Key.Length);
var hashBytes = hashFunc.ComputeHash(finalBytes);
var xorSpan = hashBytes.Length / otpLength;
var opt = new char[otpLength];
for (int i = 0; i < otpLength; i++)
for (int j = startIdx; j < startIdx + xorSpan - 1; j++)
optCharIdx ^= hashBytes[j];
var shiftBy = hashBytes[startIdx + xorSpan - 1] % 4;
optCharIdx = (byte)(optCharIdx >> shiftBy);
opt[i] = GetBase32Char(optCharIdx);
static readonly char[] alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".ToCharArray();
private static char GetBase32Char(byte fiveBitIndex)
var idx = 0x1F & fiveBitIndex;
public static string OtpListToString(IEnumerable<string> list, int columnCount)
var sb = new StringBuilder();
foreach (var otpString in list)
sb.Append(otpString).Append(" ");
if (colCounter % columnCount == 0)