-
Notifications
You must be signed in to change notification settings - Fork 0
/
Hotp.cs
87 lines (75 loc) · 2.91 KB
/
Hotp.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using System.Security.Cryptography;
namespace simpleauthenticator
{
public enum HmacHashAlgorithm
{
Md5,
Sha1,
Sha256,
Sha512
}
public static class Hotp
{
public static int Generate(byte[] secretKey, long counter, int tokenLength = 6)
{
var counterBytes = ToBigEndianBytes(counter);
return Generate(secretKey, counterBytes, tokenLength);
}
public static int Generate(byte[] secretKey, byte[] counter, int tokenLength = 6, HmacHashAlgorithm hashAlgorithm = HmacHashAlgorithm.Sha1)
{
if (counter.Length > 8)
{
throw new ArgumentOutOfRangeException(nameof(counter), counter.Length, "Counter byte array size must be exactly 8 bytes");
}
if (tokenLength > 8)
{
throw new ArgumentOutOfRangeException(nameof(tokenLength), tokenLength, "Token length must be between 1 and 8");
}
return (int)Truncate(Transform(secretKey, counter, hashAlgorithm), tokenLength);
}
private static byte[] Transform(byte[] key, byte[] data, HmacHashAlgorithm hashFunction)
{
KeyedHashAlgorithm keyedHashAlgorithm;
switch (hashFunction)
{
case HmacHashAlgorithm.Md5:
keyedHashAlgorithm = new HMACMD5(key);
break;
case HmacHashAlgorithm.Sha1:
keyedHashAlgorithm = new HMACSHA1(key);
break;
case HmacHashAlgorithm.Sha256:
keyedHashAlgorithm = new HMACSHA256(key);
break;
case HmacHashAlgorithm.Sha512:
keyedHashAlgorithm = new HMACSHA512(key);
break;
default:
throw new InvalidOperationException($"Algorithm '{hashFunction}' is not supported.");
}
return keyedHashAlgorithm.ComputeHash(data);
}
private static uint Truncate(byte[] digest, int tokenLength)
{
var offset = digest[19] & 0xf;
var token = (uint)((digest[offset] & 0x7f) << 24)
| (uint)((digest[offset + 1] & 0xff) << 16)
| (uint)((digest[offset + 2] & 0xff) << 8)
| (uint)(digest[offset + 3] & 0xff);
return token % (uint)Math.Pow(10, tokenLength);
}
private static byte[] ToBigEndianBytes(long input)
{
var bytes = new byte[8];
bytes[0] = (byte)(input >> 56);
bytes[1] = (byte)(input >> 48);
bytes[2] = (byte)(input >> 40);
bytes[3] = (byte)(input >> 32);
bytes[4] = (byte)(input >> 24);
bytes[5] = (byte)(input >> 16);
bytes[6] = (byte)(input >> 8);
bytes[7] = (byte)input;
return bytes;
}
}
}