TOTP 介绍及基于C#的简单实现
TOTP 介绍及基于C#的简单实现
Intro
TOTP 是基于时间的一次性密码生成算法,它由 RFC 6238 定义。和基于事件的一次性密码生成算法不同 HOTP,TOTP 是基于时间的,它和 HOTP 具有如下关系:
TOTP = HOTP(K, T)
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
其中:
- T:T = (Current Unix time - T0) / X, T0 = 0,X = 30
- K:客户端和服务端的共享密钥,不同的客户端的密钥各不相同。
- HOTP:该算法请参考 RFC,也可参考 理解 HMAC-Based One-Time Password Algorithm
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://tools.ietf.org/html/rfc4226
- https://tools.ietf.org/html/rfc6238
- http://wsfdl.com/algorithm/2016/04/05/理解HOTP.html
- http://wsfdl.com/algorithm/2016/04/14/理解TOTP.html
- https://www.cnblogs.com/voipman/p/6216328.html
TOTP 介绍及基于C#的简单实现的更多相关文章
- SpringBoot基于数据库实现简单的分布式锁
本文介绍SpringBoot基于数据库实现简单的分布式锁. 1.简介 分布式锁的方式有很多种,通常方案有: 基于mysql数据库 基于redis 基于ZooKeeper 网上的实现方式有很多,本文主要 ...
- Spark 介绍(基于内存计算的大数据并行计算框架)
Spark 介绍(基于内存计算的大数据并行计算框架) Hadoop与Spark 行业广泛使用Hadoop来分析他们的数据集.原因是Hadoop框架基于一个简单的编程模型(MapReduce),它支持 ...
- 基于RxJava2+Retrofit2简单易用的网络请求实现
代码地址如下:http://www.demodashi.com/demo/13473.html 简介 基于RxJava2+Retrofit2实现简单易用的网络请求,结合android平台特性的网络封装 ...
- iOS之基于FreeStreamer的简单音乐播放器(模仿QQ音乐)
代码地址如下:http://www.demodashi.com/demo/11944.html 天道酬勤 前言 作为一名iOS开发者,每当使用APP的时候,总难免会情不自禁的去想想,这个怎么做的?该怎 ...
- Spring AOP 介绍与基于接口的实现
热烈推荐:超多IT资源,尽在798资源网 声明:转载文章,为防止丢失所以做此备份. 本文来自公众号:程序之心 原文地址:https://mp.weixin.qq.com/s/vo94gVyTss0LY ...
- 基于modelsim-SE的简单仿真流程—下
基于modelsim-SE的简单仿真流程—下 编译 在 WorkSpace 窗口的 counter_tst.v上点击右键,如果选择Compile selected 则编译选中的文件,Compile A ...
- 基于modelsim-SE的简单仿真流程—上
基于modelsim-SE的简单仿真流程 编写RTL功能代码 要进行功能仿真,首先得用需要仿真的模块,也就是RTL功能代码,简称待测试的模块,该模块也就是在设计下载到FPGA的电路.一个电路模块想要有 ...
- [置顶] 使用红孩儿工具箱完成基于Cocos2d-x的简单游戏动画界面
[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier 红孩儿Cocos2d-X学习园地QQ3群:205100149,47 ...
- 基于IndexedDB实现简单文件系统
现在的indexedDB已经有几个成熟的库了,比如西面这几个,任何一个都是非常出色的. 用别人的东西好处是上手快,看文档就好,要是文档不太好,那就有点尴尬了. dexie.js :A Minimali ...
随机推荐
- 接口调用(发送http请求)
// 向对应的url地址发送http请求, 并获取响应的json字符串 public String getHttpResponse(String url) { // result用 ...
- java中Iterator和ListIterator的区别与联系
首先,为什么需要使用迭代器Iterator,原有的for循环和for each不能满足需求吗? 那是因为Iterator模式是用于遍历集合类的标准访问方法.它可以把访问逻辑从不同类型的集合类中抽象出来 ...
- SSM-Spring-02:Spring的DI初步加俩个实例
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- DI:依赖注入 第一个DEMO:域属性注入 java类:(Car类和Stu类,学生有一辆小汽车) packag ...
- 关于checkpoint
Ⅰ.Checkpoint 1.1 checkpoint的作用 缩短数据库的回复时间 缓冲池不够用时,将脏页刷到磁盘 重做日志不可用时,刷新脏页 1.2 展开分析 page被缓存在bp中,page在bp ...
- C# 读取XML节点属性值
xml文件格式如下: <?xml version="1.0" encoding="UTF-8" ?> <Product type=" ...
- mongodb的设计特征
MongoDB 的设计目标是高性能.可扩展.易部署.易使用,存储数据非常方便.其主要功能特性如下. (1)面向集合存储,容易存储对象类型的数据.在MongoDB 中数据被分组存储在集合中,集合类似 ...
- 如何把Python脚本导出为exe程序
一.pyinstaller简介 pyinstaller将Python脚本打包成可执行程序,使在没有Python环境的机器上运行 最新版是pyinstaller 3.1.1.支持python2.7和py ...
- Python3 randrange() 函数
描述 randrange() 方法返回指定递增基数集合中的一个随机数,基数缺省值为1. 语法 以下是 randrange() 方法的语法: import random random.randrange ...
- 如何用VBS编写一个简单的恶搞脚本
windows系统的电脑, 首先右击桌面,选择新建-文本文档,在桌面上新建一个文本文档: 随后打开计算机或者是我的电脑,点击其中的组织(xp系统多为工具),选择下面的文件夹和搜索选项 ...
- persistent_storage_worker.go
package) ) :length],) ) :length]) } func (engine *Engine) persistentStorageInitWorker(shard int) { ...