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. 接口调用(发送http请求)

    // 向对应的url地址发送http请求, 并获取响应的json字符串    public String getHttpResponse(String url) {        // result用 ...

  2. java中Iterator和ListIterator的区别与联系

    首先,为什么需要使用迭代器Iterator,原有的for循环和for each不能满足需求吗? 那是因为Iterator模式是用于遍历集合类的标准访问方法.它可以把访问逻辑从不同类型的集合类中抽象出来 ...

  3. SSM-Spring-02:Spring的DI初步加俩个实例

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- DI:依赖注入 第一个DEMO:域属性注入 java类:(Car类和Stu类,学生有一辆小汽车) packag ...

  4. 关于checkpoint

    Ⅰ.Checkpoint 1.1 checkpoint的作用 缩短数据库的回复时间 缓冲池不够用时,将脏页刷到磁盘 重做日志不可用时,刷新脏页 1.2 展开分析 page被缓存在bp中,page在bp ...

  5. C# 读取XML节点属性值

    xml文件格式如下: <?xml version="1.0" encoding="UTF-8" ?> <Product type=" ...

  6. mongodb的设计特征

    MongoDB 的设计目标是高性能.可扩展.易部署.易使用,存储数据非常方便.其主要功能特性如下.   (1)面向集合存储,容易存储对象类型的数据.在MongoDB 中数据被分组存储在集合中,集合类似 ...

  7. 如何把Python脚本导出为exe程序

    一.pyinstaller简介 pyinstaller将Python脚本打包成可执行程序,使在没有Python环境的机器上运行 最新版是pyinstaller 3.1.1.支持python2.7和py ...

  8. Python3 randrange() 函数

    描述 randrange() 方法返回指定递增基数集合中的一个随机数,基数缺省值为1. 语法 以下是 randrange() 方法的语法: import random random.randrange ...

  9. 如何用VBS编写一个简单的恶搞脚本

    windows系统的电脑, 首先右击桌面,选择新建-文本文档,在桌面上新建一个文本文档:     随后打开计算机或者是我的电脑,点击其中的组织(xp系统多为工具),选择下面的文件夹和搜索选项     ...

  10. persistent_storage_worker.go

    package) ) :length],) ) :length]) } func (engine *Engine) persistentStorageInitWorker(shard int) {   ...