For the app I was creating, I needed to implement a way to create One-Time-Passwords (OTP's) using an HMAC algorithm. Although Nuget (and Google) provides quite some libraries and inspiration, I was not able to find a solution that actually fit my needs.
The idea of an HOTP algorithm is defined in this RFC: HOTP: An HMAC-Based One-Time Password Algorithm, so I won't tread into detail on the how-to's and why's of the HOTP.
After some browsing, blood, sweat and hacking, I came up with this solution:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
namespace Acuzio.Util
{
/// <summary>
/// This enumeration contains the algorithms which are allowed to be used for rendering the One-time-password.
/// </summary>
public enum HashAlgorithm
{
SHA1,
SHA256,
SHA384,
SHA512,
MD5
}
public class OTP
{
private byte[] secret;
private HashAlgorithm digest = HashAlgorithm.SHA512;
private int digits = 8;
// 0 1 2 3 4 5 6 7 8
private readonly int[] DIGITS_POWERS = new int[] { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
/// <summary>
/// Create a new instance of the OTP class
/// </summary>
/// <param name="secret">The secret </param>
public OTP(byte[] secret)
{
this.secret = secret;
}
/// <summary>
/// Create a new instance of the OTP class
/// </summary>
/// <param name="secret">The secret </param>
/// <param name="digits">The number of digits in the token </param>
public OTP(byte[] secret, int digits)
{
this.secret = secret;
this.digits = digits;
}
/// <summary>
/// Create a new instance of the OTP class
/// </summary>
/// <param name="secret">The secret</param>
/// <param name="digits">The number of digits in the token</param>
/// <param name="digest">The algorithm to be used</param>
public OTP(byte[] secret, int digits, HashAlgorithm digest)
{
this.secret = secret;
this.digits = digits;
this.digest = digest;
}
/// <summary>
/// Generate a One-time-password for the provided challenge or movingfactor.
/// </summary>
/// <param name="input">The challenge which needs to be used as input</param>
/// <returns>a new one time password</returns>
public string generateOTP(UInt64 input)
{
if (this.digits <= 0 || this.digits > DIGITS_POWERS.Length - 1)
{
throw new ArgumentOutOfRangeException("digits", "The number of digits should be between 1 and 8");
}
String algorithm = null;
switch (digest)
{
case HashAlgorithm.SHA1:
algorithm = MacAlgorithmNames.HmacSha1; break;
case HashAlgorithm.SHA256:
algorithm = MacAlgorithmNames.HmacSha256; break;
case HashAlgorithm.SHA384:
algorithm = MacAlgorithmNames.HmacSha384; break;
case HashAlgorithm.SHA512:
algorithm = MacAlgorithmNames.HmacSha512; break;
case HashAlgorithm.MD5:
algorithm = MacAlgorithmNames.HmacMd5; break;
default:
throw new Exception("The provided algorithm is not supported");
}
MacAlgorithmProvider provider = MacAlgorithmProvider.OpenAlgorithm(algorithm);
byte[] data = BitConverter.GetBytes(input);
byte[] hmac = new byte[] { };
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
//create a hash for the known secret and append the challenge
CryptographicHash hmacHasher = provider.CreateHash(CryptographicBuffer.CreateFromByteArray(this.secret));
hmacHasher.Append(CryptographicBuffer.CreateFromByteArray(data));
//create a hash and reset the hasher
IBuffer buffer = hmacHasher.GetValueAndReset();
CryptographicBuffer.CopyToByteArray(buffer, out hmac);
int offset = hmac[hmac.Length - 1] & 0xf;
int binary = ((hmac[offset + 0] & 0x7F) << 24) |
((hmac[offset + 1] & 0xFF) << 16) |
((hmac[offset + 2] & 0xFF) << 8) |
((hmac[offset + 3] & 0xFF));
int password = binary % DIGITS_POWERS[this.digits];
//return the OTP with zero-padding for the number of desired digits
return password.ToString(new string('0', this.digits));
}
}
}
Below an example on how to use the OTP class (e.g. unit tests).
using System;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Acuzio.Util;
using System.Text;
namespace TestProject
{
[TestClass]
public class OTPTest
{
//...
[TestMethod]
public void TestingSha512()
{
string secret = "SomeKey";
byte[] key = Encoding.UTF8.GetBytes(secret);
ulong interval = 66778;
int returnDigits = 6;
OTP generator = new OTP(key, returnDigits, HashAlgorithm.SHA512);
String otp = generator.generateOTP(interval);
Assert.IsNotNull(otp);
Assert.AreEqual(6, otp.Length);
Assert.AreEqual("539281", otp);
}
[TestMethod]
public void TestingSha1()
{
string secret = "12345678901234567890";
byte[] key = Encoding.UTF8.GetBytes(secret);
ulong[] interval = new ulong[] { 0, 1, 2 };
string[] expected = new string[] { "755224", "287082", "359152" };
int returnDigits = 6;
for (int i = 0; i < interval.Length; i++)
{
ulong nextInterval = interval[i];
OTP generator = new OTP(key, returnDigits, HashAlgorithm.SHA1);
String otp = generator.generateOTP(nextInterval);
Assert.IsNotNull(otp);
Assert.AreEqual(6, otp.Length);
Assert.AreEqual(expected[i], otp);
}
}
//...
}
}
Some side notes:
- Exception handling might be improved, but you can tailor it to your own needs;
- I opted for defined class constructors in the OTP class, as we need to have a valid state of the object, before we can use it.
- I used an array of powers, instead of using the pow-function, purely for speed.