• TOTP 介绍及基于C#的简单实现


    TOTP 介绍及基于C#的简单实现

    Intro

    TOTP 是基于时间的一次性密码生成算法,它由 RFC 6238 定义。和基于事件的一次性密码生成算法不同 HOTP,TOTP 是基于时间的,它和 HOTP 具有如下关系:

    TOTP = HOTP(K, T)
    HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
    

    其中:

    TOTP 算法是基于 HOTP 的,对于 HOTP 算法来说,HOTP 的输入一致时始终输出相同的值,而 TOTP 是基于时间来算出来的一个值,可以在一段时间内(官方推荐是30s)保证这个值是固定以实现,在一段时间内始终是同一个值,以此来达到基于时间的一次性密码生成算法,使用下来整体还不错,有个小问题,如果需要实现一个密码只能验证一次需要自己在业务逻辑里实现,只能自己实现,TOTP 只负责生成和验证。

    C# 实现 TOTP

    实现代码

    using System;
    using System.Security.Cryptography;
    using System.Text;
    
    namespace WeihanLi.Totp
    {
        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();
                }
            }
    
            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;
            }
        }
    }
    

    使用方式:

        var otp = new Totp(OtpHashAlgorithm.SHA1, 4); // 使用 SHA1算法,输出4位
        var secretKey = "12345678901234567890";
        var output = otp.Compute(secretKey);
        Console.WriteLine($"output: {output}");
        Thread.Sleep(1000 * 30);
        var verifyResult = otp.Verify(secretKey, output); // 使用默认的验证方式,30s内有效
        Console.WriteLine($"Verify result: {verifyResult}");
        verifyResult = otp.Verify(secretKey, output, TimeSpan.FromSeconds(60)); // 指定可容忍的时间差,60s内有效
        Console.WriteLine($"Verify result: {verifyResult}");
    

    输出示例:

    Reference

  • 相关阅读:
    Net
    GUI
    第三方模块
    步入大四的第一天 2020/9/3
    期末复习计划及每日更新 8/10-9/2
    成都,come back 2020/8/10
    回家第三天 2020/7/31
    连续两天的好太阳的一天 2020/7/27
    规划规划,接下来的一个月该如何安排 7/25
    紧紧张张又兴兴奋奋的一天 2020/7/24
  • 原文地址:https://www.cnblogs.com/weihanli/p/simple-implement-on-totp-via-csharp.html
Copyright © 2020-2023  润新知