ワンタイムパスワードHOTP/TOTP
OTPワンタイムパスワードOTPはOne Time Passwordの略、すなわちワンタイムパスワードです.普段の生活の中で、私たちは使い捨てのパスワードに触れるシーンが非常に多く、例えばアカウントにログインしたり、パスワードを取り戻したり、パスワードを変更したり、振り替え操作をしたりするシーンがあります.
携帯メール+メール認証コード;
メール+メール認証コード;
Microsoft Authenticator App、Google Authenticator Appなどの認証器ソフトウェア+認証コード;
ハードウェア+認証コード:例えばネットバンクの電子暗号器;
これらのシーンの流れは、一般的に、ユーザーがアカウント+パスワードを提供した上で、ユーザーに使い捨ての検証コードを提供して、追加のセキュリティ保護を提供するようにします.通常、この検証コードは6~8ビットの数字であり、1回しか使用できないか、5分以内などの短い時間でしか使用できません.
HOTPメッセージ認証コードに基づくワンタイムパスワードHOTPは、HMAC−Based One Time Passwordの略であり、HMAC(Hashに基づくメッセージ認証コード)によるワンタイムパスワードである.アルゴリズムの詳細はRFC 4226(https://tools.ietf.org/html/rfc4226)、アルゴリズム式は、HOTP(Key,Counter)、分解はTruncate(HMAC-SHA-1(Key,Counter))である.
Key:鍵;
Counter:カウンタ;
HMAC-SHA-1:SHA 1のHMACアルゴリズムの関数に基づいて、MACの値を返し、MACは20 bytes(160 bits)のバイト配列である.
Truncate:数字を切り取る関数で、3のMACをパラメータとし、指定された規則に従って、6ビットまたは8ビットの数字を得る
TOTP時間ベースのワンタイムパスワードTOTPはTime-Based One Time Passwordの略です.TOTPはHOTPに基づいて拡張されたアルゴリズムであり、アルゴリズムの詳細はRFC 6238(https://tools.ietf.org/html/rfc6238)は、HOTPのcounterを時間Tに変換することが核心であり、現在の時間のタイムスタンプ(unixtime)と簡単に理解できる.一般的に実際の応用では,30秒,60秒,120秒など,1時間のステップ長が固定されている,すなわち,このステップ長の時間内にTOTPアルゴリズムに基づいて算出されるOTP値は同じである.
例:
Nuget:
Rfc6238AuthenticationService.cs
ソース:https://github.com/aspnet/AspNetCore AspNetCore/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs
Program.cs
携帯メール+メール認証コード;
メール+メール認証コード;
Microsoft Authenticator App、Google Authenticator Appなどの認証器ソフトウェア+認証コード;
ハードウェア+認証コード:例えばネットバンクの電子暗号器;
これらのシーンの流れは、一般的に、ユーザーがアカウント+パスワードを提供した上で、ユーザーに使い捨ての検証コードを提供して、追加のセキュリティ保護を提供するようにします.通常、この検証コードは6~8ビットの数字であり、1回しか使用できないか、5分以内などの短い時間でしか使用できません.
HOTPメッセージ認証コードに基づくワンタイムパスワードHOTPは、HMAC−Based One Time Passwordの略であり、HMAC(Hashに基づくメッセージ認証コード)によるワンタイムパスワードである.アルゴリズムの詳細はRFC 4226(https://tools.ietf.org/html/rfc4226)、アルゴリズム式は、HOTP(Key,Counter)、分解はTruncate(HMAC-SHA-1(Key,Counter))である.
Key:鍵;
Counter:カウンタ;
HMAC-SHA-1:SHA 1のHMACアルゴリズムの関数に基づいて、MACの値を返し、MACは20 bytes(160 bits)のバイト配列である.
Truncate:数字を切り取る関数で、3のMACをパラメータとし、指定された規則に従って、6ビットまたは8ビットの数字を得る
TOTP時間ベースのワンタイムパスワードTOTPはTime-Based One Time Passwordの略です.TOTPはHOTPに基づいて拡張されたアルゴリズムであり、アルゴリズムの詳細はRFC 6238(https://tools.ietf.org/html/rfc6238)は、HOTPのcounterを時間Tに変換することが核心であり、現在の時間のタイムスタンプ(unixtime)と簡単に理解できる.一般的に実際の応用では,30秒,60秒,120秒など,1時間のステップ長が固定されている,すなわち,このステップ長の時間内にTOTPアルゴリズムに基づいて算出されるOTP値は同じである.
例:
Nuget:
System.Security.Cryptography.Algorithms
Rfc6238AuthenticationService.cs
ソース:https://github.com/aspnet/AspNetCore AspNetCore/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
using System.Net;
using System.Security.Cryptography;
namespace OTP
{
using System;
using System.Text;
internal static class Rfc6238AuthenticationService
{
private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3);
private static readonly Encoding _encoding = new UTF8Encoding(false, true);
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
// Generates a new 80-bit security token
public static byte[] GenerateRandomKey()
{
byte[] bytes = new byte[20];
_rng.GetBytes(bytes);
return bytes;
}
internal static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier)
{
// # of 0's = length of pin
const int Mod = 1000000;
// See https://tools.ietf.org/html/rfc4226
// We can add an optional modifier
var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber));
var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier));
// Generate DT string
var offset = hash[hash.Length - 1] & 0xf;
Debug.Assert(offset + 4 < hash.Length);
var binaryCode = (hash[offset] & 0x7f) << 24
| (hash[offset + 1] & 0xff) << 16
| (hash[offset + 2] & 0xff) << 8
| hash[offset + 3] & 0xff;
return binaryCode % Mod;
}
private static byte[] ApplyModifier(byte[] input, string modifier)
{
if (string.IsNullOrEmpty(modifier))
{
return input;
}
var modifierBytes = _encoding.GetBytes(modifier);
var combined = new byte[checked(input.Length + modifierBytes.Length)];
Buffer.BlockCopy(input, 0, combined, 0, input.Length);
Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length);
return combined;
}
// More info: https://tools.ietf.org/html/rfc6238#section-4
private static ulong GetCurrentTimeStepNumber()
{
var delta = DateTime.UtcNow - _unixEpoch;
return (ulong)(delta.Ticks / _timestep.Ticks);
}
public static int GenerateCode(byte[] securityToken, string modifier = null)
{
if (securityToken == null)
{
throw new ArgumentNullException(nameof(securityToken));
}
// Allow a variance of no greater than 9 minutes in either direction
var currentTimeStep = GetCurrentTimeStepNumber();
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
return ComputeTotp(hashAlgorithm, currentTimeStep, modifier);
}
}
public static bool ValidateCode(byte[] securityToken, int code, string modifier = null)
{
if (securityToken == null)
{
throw new ArgumentNullException(nameof(securityToken));
}
// Allow a variance of no greater than 9 minutes in either direction
var currentTimeStep = GetCurrentTimeStepNumber();
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
for (var i = -2; i <= 2; i++)
{
var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier);
if (computedTotp == code)
{
return true;
}
}
}
// No match
return false;
}
}
}
Program.cs
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace OTP
{
class Program
{
static void Main(string[] args)
{
var code = Rfc6238AuthenticationService.GenerateCode(UTF8Encoding.UTF8.GetBytes("nii"));
var validateResult = Rfc6238AuthenticationService.ValidateCode(UTF8Encoding.UTF8.GetBytes("nii"), code);
//HOTP
// key
var key = UTF8Encoding.UTF8.GetBytes("xx_key");
//
var counter = UTF8Encoding.UTF8.GetBytes("xx_counter");
//otp6=068878
var otp6 = HOTP(key, counter, 6);
var otp5 = HOTP(key, counter, 6);
//otp8=33068878
var otp8 = HOTP(key, counter, 8);
//TOTP
// keyvar
key = UTF8Encoding.UTF8.GetBytes("xx_key");
// 10 ,otp
for (var i = 0; i < 100000; i++)
{
var otp = TOTP(key, 10, 6);
Console.WriteLine(otp);
Thread.Sleep(1000);
}
Console.WriteLine("Hello World!");
}
///
/// HOTP
///
///
///
///
///
public static string HOTP(byte[] key, byte[] counter, int length = 6)
{
using (var hashAlgorithm = new HMACSHA1(key))
{
var hmac = hashAlgorithm.ComputeHash(counter);
var offset = hmac[hmac.Length - 1] & 0xF;
var b1 = (hmac[offset] & 0x7F) << 24;
var b2 = (hmac[offset + 1] & 0xFF) << 16;
var b3 = (hmac[offset + 2] & 0xFF) << 8;
var b4 = (hmac[offset + 3] & 0xFF);
var code = b1 | b2 | b3 | b4;
var value = code % (int)Math.Pow(10, length);
return value.ToString().PadLeft(length, '0');
}
}
///
/// TOTP
///
///
///
///
///
public static string TOTP(byte[] key, int step = 60, int length = 6)
{
var unixTime = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
var counter = ((int)unixTime) / step;
var counterBytes = BitConverter.GetBytes(counter);
return HOTP(key, counterBytes, length);
}
}
}