基于时间的OTP生成生成错误的密钥C#

时间:2016-12-12 16:05:00

标签: c# google-authentication one-time-password base32

我现在已经实现了许多TOTP类,它们都生成了错误的输出。下面我发布了我用于最简单的代码。

我希望它能够实现,并且像Google身份验证器一样行事 - 例如代码https://gauth.apps.gbraad.nl/#main

所以我想要发生的是,在应用程序的前端,用户将输入他的秘密" BANANAKEY123"转换为" IJAU4QKOIFFUKWJRGIZQ ===="的基本字符串32。

现在在下面的构造函数中,键将是" BANANAKEY123"。但由于某种原因,它'使用此代码生成与GAuth OTP工具相同的OTP密钥。

唯一的两个合理错误是

var secretKeyBytes = Base32Encode(secretKey);

错误或我的计时功能错误。我检查了两个并且无法找到任何一个中的错误。那么有人可以帮我正确的方向吗?谢谢!

    public class Totp
{
    private readonly int digits = 6;
    private readonly HMACSHA1 hmac;
    private readonly HMACSHA256 hmac256;
    private readonly Int32 t1 = 30;
    internal int mode;

    private string secret;

    private const string allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

    public Totp(string key, int mode)
    {
        secret = key;
        this.mode = mode;
    }

    // defaults to SHA-1
    public Totp(string key)
    {
        secret = key;
        this.mode = 1;
    }


    public Totp(string base32string, Int32 t1, int digits) : this(base32string)
    {
        this.t1 = t1;
        this.digits = digits;
    }

    public Totp(string base32string, Int32 t1, int digits, int mode) : this(base32string, mode)
    {
        this.t1 = t1;
        this.digits = digits;
    }

    public String getCodeString()
    {
        return GetCode(this.secret, GetInterval(DateTime.UtcNow));
    }

    private static long GetInterval(DateTime dateTime)
    {
        TimeSpan elapsedTime = dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        return (long)elapsedTime.TotalSeconds / 30;
    }

    private static string GetCode(string secretKey, long timeIndex)
    {
        var secretKeyBytes = Base32Encode(secretKey);
        HMACSHA1 hmac = new HMACSHA1(secretKeyBytes);
        byte[] challenge = BitConverter.GetBytes(timeIndex);
        if (BitConverter.IsLittleEndian) Array.Reverse(challenge);
        byte[] hash = hmac.ComputeHash(challenge);
        int offset = hash[19] & 0xf;
        int truncatedHash = hash[offset] & 0x7f;
        for (int i = 1; i < 4; i++)
        {
            truncatedHash <<= 8;
            truncatedHash |= hash[offset + i] & 0xff;
        }
        truncatedHash %= 1000000;
        return truncatedHash.ToString("D6");
    }

    private static byte[] Base32Encode(string source)
    {
        var bits = source.ToUpper().ToCharArray().Select(c =>
            Convert.ToString(allowedCharacters.IndexOf(c), 2).PadLeft(5, '0')).Aggregate((a, b) => a + b);
        return Enumerable.Range(0, bits.Length / 8).Select(i => Convert.ToByte(bits.Substring(i * 8, 8), 2)).ToArray();
    }
}

1 个答案:

答案 0 :(得分:0)

我已经使用这段代码很长一段时间来生成基于时间的OTP,希望它有所帮助。

TotpAuthenticationService.cs

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

namespace Wteen.Infrastructure.Services
{
    /// <summary>
    /// An Time Based Implementation of RFC 6248, a variation from the OTP (One Time Password) with, a default code life time of 30 seconds.
    /// </summary>
    public sealed class TotpAuthenticationService
    {
        private readonly Encoding _encoding;
        private readonly int _length;
        private readonly TimeSpan _timestep;
        private readonly DateTime _unixEpoch;

        /// <summary>
        /// Create a new Instance of <see cref="TotpAuthenticationService"/>
        /// </summary>
        /// <param name="length">The length of the OTP</param>
        /// <param name="duration">The peried of time in which the genartion of a OTP with the result with the same value</param>
        public TotpAuthenticationService(int length, int duration = 30)
        {
            _length = length;
            _encoding = new UTF8Encoding(false, true);
            _timestep = TimeSpan.FromSeconds(duration);
            _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        }

        /// <summary>
        /// The current time step number
        /// </summary>
        private ulong CurrentTimeStepNumber => (ulong)(TimeElapsed.Ticks / _timestep.Ticks);

        /// <summary>
        /// The number of seconds elapsed since midnight UTC of January 1, 1970.
        /// </summary>
        private TimeSpan TimeElapsed => DateTime.UtcNow - _unixEpoch;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="securityToken"></param>
        /// <param name="modifier"></param>
        /// <returns></returns>
        public int GenerateCode(byte[] securityToken, string modifier = null)
        {
            if (securityToken == null)
                throw new ArgumentNullException(nameof(securityToken));

            using (var hmacshA1 = new HMACSHA1(securityToken))
            {
                return ComputeTotp(hmacshA1, CurrentTimeStepNumber, modifier);
            }
        }

        /// <summary>
        /// Validating for codes generated during the current and past code generation <see cref="timeSteps"/>
        /// </summary>
        /// <param name="securityToken">User's secerct</param>
        /// <param name="code">The code to validate</param>
        /// <param name="timeSteps">The number of time steps the <see cref="code"/> could be validated for.</param>
        /// <param name="channel">Possible channels could be user's email or mobile number where the code will be sent to</param>
        /// <returns></returns>
        public bool ValidateCode(byte[] securityToken, int code, int timeSteps, string channel = null)
        {
            if (securityToken == null)
                throw new ArgumentNullException(nameof(securityToken));

            using (var hmacshA1 = new HMACSHA1(securityToken))
            {
                for (var index = -timeSteps; index <= timeSteps; ++index)
                    if (ComputeTotp(hmacshA1, CurrentTimeStepNumber + (ulong)index, channel) == code)
                        return true;
            }

            return false;
        }

        private byte[] ApplyModifier(byte[] input, string modifier)
        {
            if (string.IsNullOrEmpty(modifier))
                return input;

            var bytes = _encoding.GetBytes(modifier);
            var numArray = new byte[checked(input.Length + bytes.Length)];
            Buffer.BlockCopy(input, 0, numArray, 0, input.Length);
            Buffer.BlockCopy(bytes, 0, numArray, input.Length, bytes.Length);
            return numArray;
        }

        private int ComputeTotp(HashAlgorithm algorithm, ulong timestepNumber, string modifier)
        {
            var bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber));
            var hash = algorithm.ComputeHash(ApplyModifier(bytes, modifier));
            var index = hash[hash.Length - 1] & 15;
            return (((hash[index] & sbyte.MaxValue) << 24) | ((hash[index + 1] & byte.MaxValue) << 16) | ((hash[index + 2] & byte.MaxValue) << 8) | (hash[index + 3] & byte.MaxValue)) % (int)Math.Pow(10, _length);
        }
    }
}