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

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

  1. SpringBoot基于数据库实现简单的分布式锁

    本文介绍SpringBoot基于数据库实现简单的分布式锁. 1.简介 分布式锁的方式有很多种,通常方案有: 基于mysql数据库 基于redis 基于ZooKeeper 网上的实现方式有很多,本文主要 ...

  2. Spark 介绍(基于内存计算的大数据并行计算框架)

    Spark 介绍(基于内存计算的大数据并行计算框架)  Hadoop与Spark 行业广泛使用Hadoop来分析他们的数据集.原因是Hadoop框架基于一个简单的编程模型(MapReduce),它支持 ...

  3. 基于RxJava2+Retrofit2简单易用的网络请求实现

    代码地址如下:http://www.demodashi.com/demo/13473.html 简介 基于RxJava2+Retrofit2实现简单易用的网络请求,结合android平台特性的网络封装 ...

  4. iOS之基于FreeStreamer的简单音乐播放器(模仿QQ音乐)

    代码地址如下:http://www.demodashi.com/demo/11944.html 天道酬勤 前言 作为一名iOS开发者,每当使用APP的时候,总难免会情不自禁的去想想,这个怎么做的?该怎 ...

  5. Spring AOP 介绍与基于接口的实现

    热烈推荐:超多IT资源,尽在798资源网 声明:转载文章,为防止丢失所以做此备份. 本文来自公众号:程序之心 原文地址:https://mp.weixin.qq.com/s/vo94gVyTss0LY ...

  6. 基于modelsim-SE的简单仿真流程—下

    基于modelsim-SE的简单仿真流程—下 编译 在 WorkSpace 窗口的 counter_tst.v上点击右键,如果选择Compile selected 则编译选中的文件,Compile A ...

  7. 基于modelsim-SE的简单仿真流程—上

    基于modelsim-SE的简单仿真流程 编写RTL功能代码 要进行功能仿真,首先得用需要仿真的模块,也就是RTL功能代码,简称待测试的模块,该模块也就是在设计下载到FPGA的电路.一个电路模块想要有 ...

  8. [置顶] 使用红孩儿工具箱完成基于Cocos2d-x的简单游戏动画界面

    [Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier 红孩儿Cocos2d-X学习园地QQ3群:205100149,47 ...

  9. 基于IndexedDB实现简单文件系统

    现在的indexedDB已经有几个成熟的库了,比如西面这几个,任何一个都是非常出色的. 用别人的东西好处是上手快,看文档就好,要是文档不太好,那就有点尴尬了. dexie.js :A Minimali ...

随机推荐

  1. EOS技术研究:合约与数据库交互

    智能合约操作链数据库是很常见的应用场景.EOS提供了专门的工具来做这件事(相当于Ethereum的leveldb),专业术语叫做持久化API,本文将完整严密地介绍这个工具以及对它的使用测试. 关键字: ...

  2. C++ Singleton (单例) 模式最优实现

    参考:http://blog.yangyubo.com/2009/06/04/best-cpp-singleton-pattern/ 索引 静态化并不是单例 (Singleton) 模式 饿汉模式 懒 ...

  3. SpringMVC中的拦截器

    1. 自定义拦截器 实现HandlerInterceptor接口 拦截器一: package cn.rodge.ssm.interceptor;import javax.servlet.http.Ht ...

  4. ExecutorCompletionService分析及使用

    当我们通过Executor提交一组并发执行的任务,并且希望在每一个任务完成后能立即得到结果,有两种方式可以采取: 方式一: 通过一个list来保存一组future,然后在循环中轮训这组future,直 ...

  5. C语言实现计算双基回文数详解

    双基回文数的定义: 如果一个正整数n至少在两个不同的进位制(二进制<=进制=<十进制)b1和b2下都是回文数,则称n是双基回文数. 根据定义,简单的说就是在二进制到十进制之间(包括十进制和 ...

  6. CKEditor、UBB编辑器的使用总结

    2018-04-08 22:07:16.188 ERROR 14728 --- [nio-8082-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Se ...

  7. HTML5 CSS3 专题 :诱人的实例 3D旋转木马效果相册

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/32964301 首先说明一下创意的出处:http://www.zhangxinxu ...

  8. [Python] uniform() 函数

    描述uniform() 方法将随机生成下一个实数,它在[x, y) 范围内. 语法以下是 uniform() 方法的语法: import random random.uniform(x, y) 注意: ...

  9. Python logger 没打出行数

    # !/user/bin/python # -*- coding: utf-8 -*- ''' subprocess : 需要在linux平台上测试 shell logging ''' import ...

  10. readonly 和 disabled的区别

    在开发的时候遇到了disabled不能传值的问题 但是readonly可以传值 学习源头: http://www.w3school.com.cn/tags/att_input_readonly.asp ...