Created
September 28, 2016 13:23
-
-
Save cameronsjo/f395a50990569a17a3a924d798aece0a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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