0

I am trying to implemente a TOTP algorithms, but the final result isn't match the expected result.

namespace TotpToken;

using System.Security.Cryptography;
using System.Text;

public enum OtpHashAlgorithm
{
    SHA1 = 0,
    SHA256 = 1,
    SHA512 = 2
}

public class Totp
{
    private readonly OtpHashAlgorithm _hashAlgorithm;
    private readonly int _codeSize;

    public Totp() : this(OtpHashAlgorithm.SHA1, 6)
    {
    }

    public Totp(OtpHashAlgorithm otpHashAlgorithm, int codeSize)
    {
        _hashAlgorithm = otpHashAlgorithm;

        // valid input parameter
        if (codeSize <= 0 || codeSize > 10)
        {
            throw new ArgumentOutOfRangeException(nameof(codeSize), codeSize, "length must between 1 and 9");
        }
        _codeSize = codeSize;
    }

    private static readonly Encoding Encoding = new UTF8Encoding(false, true);

    public virtual string Compute(string securityToken) => Compute(Encoding.GetBytes(securityToken));

    public virtual string Compute(byte[] securityToken) => Compute(securityToken, GetCurrentTimeStepNumber());

    private string Compute(byte[] securityToken, long counter)
    {
        HMAC hmac;
        switch (_hashAlgorithm)
        {
            case OtpHashAlgorithm.SHA1:
                hmac = new HMACSHA1(securityToken);
                break;

            case OtpHashAlgorithm.SHA256:
                hmac = new HMACSHA256(securityToken);
                break;

            case OtpHashAlgorithm.SHA512:
                hmac = new HMACSHA512(securityToken);
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(_hashAlgorithm), _hashAlgorithm, null);
        }

        using (hmac)
        {
            var stepBytes = BitConverter.GetBytes(counter);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(stepBytes); // need BigEndian
            }
            // See https://tools.ietf.org/html/rfc4226
            var hashResult = hmac.ComputeHash(stepBytes);

            var offset = hashResult[hashResult.Length - 1] & 0xf;
            var p = "";
            for (var i = 0; i < 4; i++)
            {
                p += hashResult[offset + i].ToString("X2");
            }
            var num = Convert.ToInt64(p, 16) & 0x7FFFFFFF;

            //var binaryCode = (hashResult[offset] & 0x7f) << 24
            //                 | (hashResult[offset + 1] & 0xff) << 16
            //                 | (hashResult[offset + 2] & 0xff) << 8
            //                 | (hashResult[offset + 3] & 0xff);

            return (num % (int)Math.Pow(10, _codeSize)).ToString().PadLeft(_codeSize, '0');
        }
    }

    public virtual bool Verify(string securityToken, string code) => Verify(Encoding.GetBytes(securityToken), code);

    public virtual bool Verify(string securityToken, string code, TimeSpan timeToleration) => Verify(Encoding.GetBytes(securityToken), code, timeToleration);

    public virtual bool Verify(byte[] securityToken, string code) => Verify(securityToken, code, TimeSpan.Zero);

    public virtual bool Verify(byte[] securityToken, string code, TimeSpan timeToleration)
    {
        var futureStep = (int)(timeToleration.TotalSeconds / 30);
        var step = GetCurrentTimeStepNumber();
        for (int i = -futureStep; i <= futureStep; i++)
        {
            if (step + i < 0)
            {
                continue;
            }
            var totp = Compute(securityToken, step + i);
            if (totp == code)
            {
                return true;
            }
        }
        return false;
    }

    private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    /// <summary>
    /// timestep
    /// 30s(Recommend)
    /// </summary>
    private static readonly long _timeStepTicks = TimeSpan.TicksPerSecond * 30;

    // More info: https://tools.ietf.org/html/rfc6238#section-4
    private static long GetCurrentTimeStepNumber()
    {
        var delta = DateTime.UtcNow - _unixEpoch;
        return delta.Ticks / _timeStepTicks;
    }
}
public class Program
{
    public static void Main()
    {
        var totp = new Totp(OtpHashAlgorithm.SHA1, 6);
        var token = "I65VU7K5ZQL7WB4E";
        var code = totp.Compute(token);

        Console.WriteLine(code);
    }
}

I found this code from the internet on how to implement RFC6238 (TOTP), which I thought would work, but in reality this string of code is not the same as the code displayed by the mobile 2-step verification app. I'm not quite sure what in this code would cause this problem, please help me. The secret key is "I65VU7K5ZQL7WB4E"

Seamain
  • 3
  • 1

0 Answers0