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/%E7%90%86%E8%A7%A3HOTP.html
http://wsfdl.com/algorithm/2016/04/14/%E7%90%86%E8%A7%A3TOTP.html
https://www.cnblogs.com/voipman/p/6216328.html
TOTP 介绍及基于 C# 的简单实现相关推荐
- TOTP 介绍及基于C#的简单实现
TOTP 介绍及基于C#的简单实现 Intro TOTP 是基于时间的一次性密码生成算法,它由 RFC 6238 定义.和基于事件的一次性密码生成算法不同 HOTP,TOTP 是基于时间的,它和 HO ...
- springboot mysql行锁_SpringBoot基于数据库实现简单的分布式锁
本文介绍SpringBoot基于数据库实现简单的分布式锁. 1.简介 分布式锁的方式有很多种,通常方案有: 基于mysql数据库 基于redis 基于ZooKeeper 网上的实现方式有很多,本文主要 ...
- mysql binlog update_mysql binlog 简单介绍与基于binlog数据恢复
mysql binlog 简单介绍与基于binlog数据恢复 通过备份文件恢复 binlog(本节重点) binlog 二进制日志文件 show variables like 'log_bin'; 二 ...
- 基于Golang的简单web服务程序开发——CloudGo
基于Golang的简单web服务程序开发--CloudGo[阅读时间:约10分钟] 一.概述 二.系统环境&项目介绍 1.系统环境 2.项目的任务要求 (1)基本要求 (2)扩展要求 三.具体 ...
- Microwindows及基于Nano-X的简单程序开发
http://www.rdxx.com 05年09月13日 22:26 Blog.ChinaUnix.net Nano-X是一种图形编程接口,和Win32一样,在上面我们可以编写自己的应用程序,下面转 ...
- 基于RxJava2+Retrofit2简单易用的网络请求实现
代码地址如下: http://www.demodashi.com/demo/13473.html 简介 基于RxJava2+Retrofit2实现简单易用的网络请求,结合android平台特性的网络封 ...
- iOS之基于FreeStreamer的简单音乐播放器(模仿QQ音乐)
代码地址如下: http://www.demodashi.com/demo/11944.html 天道酬勤 前言 作为一名iOS开发者,每当使用APP的时候,总难免会情不自禁的去想想,这个怎么做的?该 ...
- 基于人人网的简单爬虫(一)——正则表达式
应课程实验要求,要写一个基于人人网的简单爬虫.实验要求如下: 学会使用一种编程语言实现爬取人人网关系网络的程序.该程序功能如下: 1. 能够输入用户登陆所产生的cookie,允许爬虫对人人网进行爬取 ...
- python documents in chinese_基于 Python 的简单自然语言处理实践
基于 Python 的简单自然语言处理 Twenty News Group 语料集处理 20 Newsgroup 数据集包含了约 20000 篇来自于不同的新闻组的文档,最早由 Ken Lang 搜集 ...
最新文章
- java的byte与C#的异同引起的字符处理问题。
- 将数据导入到mysql_06955.10.2如何将CM的外部PostgreSQL数据库迁移至MySQL服务
- 【ubuntu+opencv3】ubuntu16.04+qt5+opencv3.2.0编译与安装
- 华北电力大学计算机科学与技术考研,华北电力大学吴克河教授谈计算机科学与技术专业...
- HDU 5869.Different GCD Subarray Query-区间gcd+树状数组 (神奇的标记右移操作) (2016年ICPC大连网络赛)...
- ios9和xcode7的适配问题
- [CF]Codeforces Round #546 (Div. 2)
- 第7章[7.18] Ext JS组件嵌入HTML页面
- android 人脸识别边框_虹软人脸识别 - Android Camera实时人脸追踪画框适配
- 2020年C题认证杯SEIR模型参数拟合
- 微博、微信朋友圈、QQ空间功能对比
- AJ-Report 初学(入门教程)
- android 磁场传感器应用
- EVE模拟器关联CRT与Wireshark
- 达梦数据库 find_in_set 函数适配
- 基于Python的毕业论文怎么写?
- 网友吐槽常被盒马种草 但你知道带货王是怎么来的吗?
- “SqlSession[xxx] was not registered for synchronization because synchronization is not active”问题成功解决
- PDF转CAD怎么转?试试这个办法
- arduino的C语言基础
热门文章
- sketch怎么移动图层_什么是Photoshop Express,Fix,Mix和Sketch移动应用程序?
- java 重置定时器_可重置Java定时器
- Linux之ACL权限控制
- 15个IT技术人员必须思考的问题
- loadrunner 参数化数据更新方式
- Android系统如何实现UI的自适应
- 第二十五章补充内容 5 不能为0的变量
- 黄聪:Microsoft Enterprise Library 5.0 系列教程(四) Logging Application Block
- ASP.NET Core Web API使用静态swagger.json文件
- Blazor VS React / Angular / Vue.js