TOTP 介绍及基于C#的简单实现
2019-04-22 16:09
295 查看
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
相关文章推荐
- 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。
- c#基于socket编程实现简单多人聊天程序
- C#串口介绍以及简单串口通信程序设计实现
- C#串口介绍以及简单串口通信程序设计实现
- 用C# 实现简单的p2p(基于TCP)
- 基于c#实现的九九乘法表(简单实例)
- C#实现基于IE内核的简单浏览器完整实例
- 用C#实现基于查寻字符串的文件行查询器(2)-相关技术介绍
- 一个简单的AJAX实现,基于C#的ASP.Net,包括服务器端的程序代码
- 基于C#实现简单的随机抽奖小程序
- [转载] STL allocator的介绍和一个基于malloc/free的allocator的简单实现
- C#串口介绍以及简单串口通信程序设计实现
- 基于C#中的Trace实现一个简单的日志系统
- 基于ASP.net C#技术来实现,介绍如何处理Session对象变量失效的问题
- 基于C#实现简单离线注册码生成与验证
- 一个简单的AJAX实现,基于C#的ASP.Net,包括服务器端的程序代码
- [转载] STL allocator的介绍和一个基于malloc/free的allocator的简单实现
- 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。
- 基于逻辑运算的简单权限系统(实现) JS 版
- 用C#实现基于TCP协议的网络通讯