-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHotp.cs
103 lines (89 loc) · 3.64 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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using System.Security.Cryptography;
namespace simpleauthenticator
{
/// <summary>
/// Implements HOTP tokens generation according RFC4226: https://www.rfc-editor.org/rfc/rfc4226.
/// </summary>
public static class Hotp
{
public const int DefaultTokenLength = 6;
public const HmacAlgorithm DefaultHmacAlgorithm = HmacAlgorithm.Sha1;
public static HotpToken Generate(
byte[] secretKey,
long counter,
int tokenLength = DefaultTokenLength,
HmacAlgorithm hashAlgorithm = DefaultHmacAlgorithm)
{
var counterBytes = ToBigEndianBytes(counter);
return Generate(secretKey, counterBytes, tokenLength, hashAlgorithm);
}
public static HotpToken Generate(
byte[] secretKey,
byte[] counter,
int tokenLength = DefaultTokenLength,
HmacAlgorithm hashAlgorithm = DefaultHmacAlgorithm)
{
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");
}
var transformResult = GenerateDigest(secretKey, counter, hashAlgorithm);
return new HotpToken(
(int)Truncate(transformResult, tokenLength));
}
private static byte[] GenerateDigest(byte[] key, byte[] data, HmacAlgorithm hashFunction)
{
KeyedHashAlgorithm keyedHashAlgorithm;
switch (hashFunction)
{
case HmacAlgorithm.Md5:
keyedHashAlgorithm = new HMACMD5(key);
break;
case HmacAlgorithm.Sha1:
keyedHashAlgorithm = new HMACSHA1(key);
break;
case HmacAlgorithm.Sha2_256:
keyedHashAlgorithm = new HMACSHA256(key);
break;
case HmacAlgorithm.Sha2_512:
keyedHashAlgorithm = new HMACSHA512(key);
break;
case HmacAlgorithm.Sha3_256:
keyedHashAlgorithm = new HMACSHA3_256(key);
break;
case HmacAlgorithm.Sha3_512:
keyedHashAlgorithm = new HMACSHA3_512(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[digest.Length - 1] & 0xf; // last byte
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;
}
}
}