Skip to content

Instantly share code, notes, and snippets.

@cameronsjo
Created September 28, 2016 13:23
Show Gist options
  • Select an option

  • Save cameronsjo/f395a50990569a17a3a924d798aece0a to your computer and use it in GitHub Desktop.

Select an option

Save cameronsjo/f395a50990569a17a3a924d798aece0a to your computer and use it in GitHub Desktop.
public static class Base32
{
public static byte[] ToBytes(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException("input");
}
input = input.TrimEnd('='); //remove padding characters
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;
foreach (var c in input)
{
var cValue = CharToValue(c);
if (bitsRemaining > 5)
{
mask = cValue << bitsRemaining - 5;
curByte = (byte) (curByte | mask);
bitsRemaining -= 5;
}
else
{
mask = cValue >> 5 - bitsRemaining;
curByte = (byte) (curByte | mask);
returnArray[arrayIndex++] = curByte;
curByte = (byte) (cValue << 3 + bitsRemaining);
bitsRemaining += 3;
}
}
//if we didn't end with a full byte
if (arrayIndex != byteCount)
{
returnArray[arrayIndex] = curByte;
}
return returnArray;
}
public static string ToString(byte[] input)
{
if (input == null ||
input.Length == 0)
{
throw new ArgumentNullException("input");
}
var charCount = (int) Math.Ceiling(input.Length/5d)*8;
var returnArray = new char[charCount];
byte nextChar = 0, bitsRemaining = 5;
var arrayIndex = 0;
foreach (var b in input)
{
nextChar = (byte) (nextChar | b >> 8 - bitsRemaining);
returnArray[arrayIndex++] = ValueToChar(nextChar);
if (bitsRemaining < 4)
{
nextChar = (byte) (b >> 3 - bitsRemaining & 31);
returnArray[arrayIndex++] = ValueToChar(nextChar);
bitsRemaining += 5;
}
bitsRemaining -= 3;
nextChar = (byte) (b << bitsRemaining & 31);
}
//if we didn't end with a full char
if (arrayIndex != charCount)
{
returnArray[arrayIndex++] = ValueToChar(nextChar);
while (arrayIndex != charCount)
{
returnArray[arrayIndex++] = '='; //padding
}
}
return new string(returnArray);
}
private static int CharToValue(char 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");
}
private static char ValueToChar(byte b)
{
if (b < 26)
{
return (char) (b + 65);
}
if (b < 32)
{
return (char) (b + 24);
}
throw new ArgumentException("Byte is not a value Base32 value.", "b");
}
}
public class OneTimePasswordGenerator
{
private string _identity;
private int _secondsToGo;
public OneTimePasswordGenerator()
{
var timer = new Timer
{
Interval = TimeSpan.FromMilliseconds(500).TotalMilliseconds
};
timer.Elapsed += (s, e) => SecondsToGo = 30 - Convert.ToInt32(GetUnixTimestamp()%30);
timer.Enabled = true;
Secret = new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21, 0xDE, 0xAD, 0xBE, 0xEF };
Identity = "user@host.com";
}
public int SecondsToGo
{
get { return _secondsToGo; }
private set
{
_secondsToGo = value;
if (SecondsToGo == 30)
{
CalculateOneTimePassword();
}
}
}
public string Identity
{
get { return _identity; }
set
{
_identity = value;
CalculateOneTimePassword();
}
}
public string SecretBase32
{
get { return Base32.ToString(Secret); }
set
{
try
{
Secret = Base32.ToBytes(value);
}
catch
{
}
;
}
}
public byte[] Secret { get; set; }
public string QrCodeUrl
{
get { return GetQRCodeUrl(); }
}
public long Timestamp { get; private set; }
public byte[] Hmac { get; private set; }
public byte[] HmacPart1
{
get { return Hmac.Take(Offset).ToArray(); }
}
public byte[] HmacPart2
{
get { return Hmac.Skip(Offset).Take(4).ToArray(); }
}
public byte[] HmacPart3
{
get { return Hmac.Skip(Offset + 4).ToArray(); }
}
public int Offset { get; private set; }
public int OneTimePassword { get; set; }
private string GetQRCodeUrl()
{
// https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
return string.Format("https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/{0}%3Fsecret%3D{1}", Identity, SecretBase32);
}
private void CalculateOneTimePassword()
{
// https://tools.ietf.org/html/rfc4226
Timestamp = Convert.ToInt64(GetUnixTimestamp()/30);
var data = BitConverter.GetBytes(Timestamp).Reverse().ToArray();
Hmac = new HMACSHA1(Secret).ComputeHash(data);
Offset = Hmac.Last() & 0x0F;
OneTimePassword = (
(Hmac[Offset + 0] & 0x7f) << 24 |
(Hmac[Offset + 1] & 0xff) << 16 |
(Hmac[Offset + 2] & 0xff) << 8 |
Hmac[Offset + 3] & 0xff
)%1000000;
}
private static long GetUnixTimestamp()
{
return DateTimeOffset.Now.ToUnixTimeSeconds();
// return Convert.ToInt64(Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment