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

原文地址:https://www.cnblogs.com/weihanli/p/simple-implement-on-totp-via-csharp.html

时间: 2024-08-11 21:33:58

TOTP 介绍及基于C#的简单实现的相关文章

Windows Workflow Foundation技术介绍(基于.NET Framework 4.5)

Windows Workflow Foundation技术介绍(基于.NET Framework 4.5) 转自:http://www.cpiso.cn/jsyj/ghxx/2014/5/15/459.shtml Microsoft Windows Workflow Foundation (WF) 是一个可扩展框架,用于在 Windows 平台上开发工作流解决方案.Windows Workflow Foundation 同时提供了 API 和一些工具,用于开发和执行基于工作流的应用程序.Wind

&nbsp; &nbsp; &nbsp; &nbsp; 基于bind的简单DNS搭建

我们都知道互联网通信是基于IP地址的,然而我们在访问一个网站的时候只需输入主机名(有时也指我们所说的域名)即可实现,那是因为我们在背后用到了将主机名解释为了对应的IP地址的机制--DNS.下面我们来介绍DNS的实现过程. 一:bind的安装配置(正反解析): 1.bind 介绍:bind:bekerleyinternet name domain,我们简单的理解它是用bind 工具实 现DNS服务器的配置. 2.bind 安装:bind 安装比较简单我们可以使用下面命令安装并查看安装bind都生成

基于Java实现简单Http服务器(转)

基于Java实现简单Http服务器(转) 本文将详细介绍如何基于java语言实现一个简单的Http服务器,文中将主要介绍三个方面的内容:1)Http协议的基本知识.2)java.net.Socket类.3)java.net.ServerSocket类,读完本文后你可以把这个服务器用多线程的技术重新编写一个更好的服务器.           由于Web服务器使用Http协议通信的因此也把它叫做Http服务器,Http使用可靠的TCP连接来工作,它是面向连接的通信方式,这意味着客户端和服务器每次通信

Hibernate中,基于Annotation的简单树形结构的实现

在系统设计中,经常用到递归性质的树形结果,比如菜单.多级分类等,一般是在同一个表中定义父子关系实现这种结构. 下面是在Hibernate中,基于Annotation的简单树形结构的实现: 第一步:创建Entity类,并添加注解实现关联关系    ps: 主要是利用@ManyToOne 和 @OneToMany 配置在同一个Entity类中实现树形递归的结构.hibernate注解形式比在xml配置更加简洁 TreeNode.java 1 package com.hfut.hibernate; 2

基于 lua-resty-upload 实现简单的文件上传服务

今天了解了一下 lua-resty-upload 模块,并基于 lua-resty-upload 模块简单实现了一个基本的表单文件上传服务. lua-resty-upload 在 github 上的项目地址为: https://github.com/openresty/lua-resty-upload 从实现可以看到,其实 upload 服务的实现还是比较简单的,就一个源文件 lualib/resty/upload.lua,总的代码行数也只有 300 行不到. 下面我整理了一下搭建文件上传服务的

jmGraph:一个基于html5的简单画图组件

jmGraph:一个基于html5的简单画图组件 特性: 代码书写简单易理解 面向对象的代码结构 对图形控件化 样式抽离 模块化:入seajs实现模块化开发 兼容性:暂只推荐支持html5的浏览器:ie9+,chrome,firefox等. jiamao/jmgraph · GitHub APIs jmgraph是一个基于html5的WEB前端画图组件. 前端画图对象控件化,支持鼠标和健盘事件响应,可对单个控件样式设定,支持简单的动画处理.可大大地简化前端画图.

(8)基于hadoop的简单网盘应用实现4

文件结构 (1).index.jsp首页面实现 index.jsp <%@ include file="head.jsp"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="org.apache.hadoop.fs.FileSta

基于AVR128的简单Modbus协议实现

Modbus通讯协议是由Modicon公司在1979年开发的,应用于工业现场控制的总线协议.Modbus通讯系统包括带有可编程控制的芯片节点和公共传输线组成,其目的是用于多节点数据的采集和监控.Modbus协议采用主从模式,通讯系统中有一个主机对多个节点从机进行监控,从机节点最多支持247个.每个从机均有自己独立的从机地址,而且改地址能够被主机识别. 能够支持Modbus协议的通讯系统有RS-232,RS-422,RS-485等.同时Modbus协议具有标准.开放.免费.帧格式简单等特点而被广大

Java 并发专题 : Executor详细介绍 打造基于Executor的Web服务器

适配器模式,将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 应用场景:系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配.适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况. 代码实现: //Adapter.h #include "stdafx.h" #include <iostream> class Adaptee