2016-12-12 9 views
0

私はTOTPクラスをいくつか実装しましたが、それらはすべて間違った出力を生成します。以下では、最もシンプルなコードを使用しました。時間ベースのOTP生成で間違った鍵が生成されるC#

Google認証システムと同じように実装して動作させたいと思います(コードhttps://gauth.apps.gbraad.nl/#mainなど)。

私は、アプリケーションのフロントエンドで、 "IJAU4QKOIFFUKWJRGIZQ ===="のbase32文字列に変換する秘密の "BANANAKEY123"を入力します。

以下のコンストラクタで、キーは「BANANAKEY123」になります。しかし、何らかの理由で、このコードでGAuth OTPツールと同じOTPキーが生成されるわけではありません。

2つのだけの合理的なミスは

var secretKeyBytes = Base32Encode(secretKey); 

が間違っているか、私のタイミング機能が間違っているということでしょう。私は両方をチェックし、それらのいずれかに欠陥を見つけることができませんでした。だから、誰かが正しい方向に私を助けてくれますか?ありがとうございました!

public class Totp 
{ 
    private readonly int digits = 6; 
    private readonly HMACSHA1 hmac; 
    private readonly HMACSHA256 hmac256; 
    private readonly Int32 t1 = 30; 
    internal int mode; 

    private string secret; 

    private const string allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 

    public Totp(string key, int mode) 
    { 
     secret = key; 
     this.mode = mode; 
    } 

    // defaults to SHA-1 
    public Totp(string key) 
    { 
     secret = key; 
     this.mode = 1; 
    } 


    public Totp(string base32string, Int32 t1, int digits) : this(base32string) 
    { 
     this.t1 = t1; 
     this.digits = digits; 
    } 

    public Totp(string base32string, Int32 t1, int digits, int mode) : this(base32string, mode) 
    { 
     this.t1 = t1; 
     this.digits = digits; 
    } 

    public String getCodeString() 
    { 
     return GetCode(this.secret, GetInterval(DateTime.UtcNow)); 
    } 

    private static long GetInterval(DateTime dateTime) 
    { 
     TimeSpan elapsedTime = dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 
     return (long)elapsedTime.TotalSeconds/30; 
    } 

    private static string GetCode(string secretKey, long timeIndex) 
    { 
     var secretKeyBytes = Base32Encode(secretKey); 
     HMACSHA1 hmac = new HMACSHA1(secretKeyBytes); 
     byte[] challenge = BitConverter.GetBytes(timeIndex); 
     if (BitConverter.IsLittleEndian) Array.Reverse(challenge); 
     byte[] hash = hmac.ComputeHash(challenge); 
     int offset = hash[19] & 0xf; 
     int truncatedHash = hash[offset] & 0x7f; 
     for (int i = 1; i < 4; i++) 
     { 
      truncatedHash <<= 8; 
      truncatedHash |= hash[offset + i] & 0xff; 
     } 
     truncatedHash %= 1000000; 
     return truncatedHash.ToString("D6"); 
    } 

    private static byte[] Base32Encode(string source) 
    { 
     var bits = source.ToUpper().ToCharArray().Select(c => 
      Convert.ToString(allowedCharacters.IndexOf(c), 2).PadLeft(5, '0')).Aggregate((a, b) => a + b); 
     return Enumerable.Range(0, bits.Length/8).Select(i => Convert.ToByte(bits.Substring(i * 8, 8), 2)).ToArray(); 
    } 
} 
+3

、あなたの 'Base32Encode'方法は非常に非効率(' TOUPPER() 'と' ToCharArray() 'の呼び出しは不要です)、代わりに' STRING'と 'Aggregate'を使用しています'StringBuilder'の新しい文字列の割り当てが過剰になり(O(n^n)時間で実行されます)。 – Dai

+0

あなたの「秘密鍵」に文字「1」が含まれていますが、それはあなたの 'allowedCharacters'セットにはないというこの投稿のタイプミスですか? –

答えて

0

タイムベースのOTPを生成するためにかなりの時間このコードを使用しています。余談として

TotpAuthenticationService.cs

using System; 
using System.Net; 
using System.Security.Cryptography; 
using System.Text; 

namespace Wteen.Infrastructure.Services 
{ 
    /// <summary> 
    /// An Time Based Implementation of RFC 6248, a variation from the OTP (One Time Password) with, a default code life time of 30 seconds. 
    /// </summary> 
    public sealed class TotpAuthenticationService 
    { 
     private readonly Encoding _encoding; 
     private readonly int _length; 
     private readonly TimeSpan _timestep; 
     private readonly DateTime _unixEpoch; 

     /// <summary> 
     /// Create a new Instance of <see cref="TotpAuthenticationService"/> 
     /// </summary> 
     /// <param name="length">The length of the OTP</param> 
     /// <param name="duration">The peried of time in which the genartion of a OTP with the result with the same value</param> 
     public TotpAuthenticationService(int length, int duration = 30) 
     { 
      _length = length; 
      _encoding = new UTF8Encoding(false, true); 
      _timestep = TimeSpan.FromSeconds(duration); 
      _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 
     } 

     /// <summary> 
     /// The current time step number 
     /// </summary> 
     private ulong CurrentTimeStepNumber => (ulong)(TimeElapsed.Ticks/_timestep.Ticks); 

     /// <summary> 
     /// The number of seconds elapsed since midnight UTC of January 1, 1970. 
     /// </summary> 
     private TimeSpan TimeElapsed => DateTime.UtcNow - _unixEpoch; 

     /// <summary> 
     /// 
     /// </summary> 
     /// <param name="securityToken"></param> 
     /// <param name="modifier"></param> 
     /// <returns></returns> 
     public int GenerateCode(byte[] securityToken, string modifier = null) 
     { 
      if (securityToken == null) 
       throw new ArgumentNullException(nameof(securityToken)); 

      using (var hmacshA1 = new HMACSHA1(securityToken)) 
      { 
       return ComputeTotp(hmacshA1, CurrentTimeStepNumber, modifier); 
      } 
     } 

     /// <summary> 
     /// Validating for codes generated during the current and past code generation <see cref="timeSteps"/> 
     /// </summary> 
     /// <param name="securityToken">User's secerct</param> 
     /// <param name="code">The code to validate</param> 
     /// <param name="timeSteps">The number of time steps the <see cref="code"/> could be validated for.</param> 
     /// <param name="channel">Possible channels could be user's email or mobile number where the code will be sent to</param> 
     /// <returns></returns> 
     public bool ValidateCode(byte[] securityToken, int code, int timeSteps, string channel = null) 
     { 
      if (securityToken == null) 
       throw new ArgumentNullException(nameof(securityToken)); 

      using (var hmacshA1 = new HMACSHA1(securityToken)) 
      { 
       for (var index = -timeSteps; index <= timeSteps; ++index) 
        if (ComputeTotp(hmacshA1, CurrentTimeStepNumber + (ulong)index, channel) == code) 
         return true; 
      } 

      return false; 
     } 

     private byte[] ApplyModifier(byte[] input, string modifier) 
     { 
      if (string.IsNullOrEmpty(modifier)) 
       return input; 

      var bytes = _encoding.GetBytes(modifier); 
      var numArray = new byte[checked(input.Length + bytes.Length)]; 
      Buffer.BlockCopy(input, 0, numArray, 0, input.Length); 
      Buffer.BlockCopy(bytes, 0, numArray, input.Length, bytes.Length); 
      return numArray; 
     } 

     private int ComputeTotp(HashAlgorithm algorithm, ulong timestepNumber, string modifier) 
     { 
      var bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber)); 
      var hash = algorithm.ComputeHash(ApplyModifier(bytes, modifier)); 
      var index = hash[hash.Length - 1] & 15; 
      return (((hash[index] & sbyte.MaxValue) << 24) | ((hash[index + 1] & byte.MaxValue) << 16) | ((hash[index + 2] & byte.MaxValue) << 8) | (hash[index + 3] & byte.MaxValue)) % (int)Math.Pow(10, _length); 
     } 
    } 
} 
+0

単なるリンク以上のものを追加することは可能ですか?リンクが悪くなった場合に備えて、あなたの答えはもはや役に立たなくなります。 – Chillie

+0

これを指摘してくれた@Chillieにありがとう、私はこれに新しいです、私はあなたの提案を将来考慮に入れます。 –

関連する問題