TOTP 介绍及基于 C# 的简单实现
TOTP 介紹及基于 C# 的簡單實(shí)現(xiàn)
Intro
TOTP 是基于時(shí)間的一次性密碼生成算法,它由 RFC 6238 定義。和基于事件的一次性密碼生成算法不同 HOTP,TOTP 是基于時(shí)間的,它和 HOTP 具有如下關(guān)系:
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:客戶端和服務(wù)端的共享密鑰,不同的客戶端的密鑰各不相同。
HOTP:該算法請參考 RFC,也可參考 理解 HMAC-Based One-Time Password Algorithm
TOTP 算法是基于 HOTP 的,對(duì)于 HOTP 算法來說,HOTP 的輸入一致時(shí)始終輸出相同的值,而 TOTP 是基于時(shí)間來算出來的一個(gè)值,可以在一段時(shí)間內(nèi)(官方推薦是30s)保證這個(gè)值是固定以實(shí)現(xiàn),在一段時(shí)間內(nèi)始終是同一個(gè)值,以此來達(dá)到基于時(shí)間的一次性密碼生成算法,使用下來整體還不錯(cuò),有個(gè)小問題,如果需要實(shí)現(xiàn)一個(gè)密碼只能驗(yàn)證一次需要自己在業(yè)務(wù)邏輯里實(shí)現(xiàn),只能自己實(shí)現(xiàn),TOTP 只負(fù)責(zé)生成和驗(yàn)證。
C# 實(shí)現(xiàn) TOTP
實(shí)現(xiàn)代碼
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); // 使用默認(rèn)的驗(yàn)證方式,30s內(nèi)有效
Console.WriteLine($"Verify result: {verifyResult}");
verifyResult = otp.Verify(secretKey, output, TimeSpan.FromSeconds(60)); // 指定可容忍的時(shí)間差,60s內(nèi)有效
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
總結(jié)
以上是生活随笔為你收集整理的TOTP 介绍及基于 C# 的简单实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core之只是多看了你一眼
- 下一篇: 微软携手红帽,共筑开源新未来