2010-11-29 2 views
5

Я пытаюсь обернуть мозг вокруг создания шестизначного/персонального, не чувствительного к регистру, одноразового пароля.Одноразовый пароль на основе HMAC в C# (RFC 4226 - HOTP)

Мой источник http://tools.ietf.org/html/rfc4226#section-5

Первое определение параметров

C  8-byte counter value, the moving factor. This counter 
     MUST be synchronized between the HOTP generator (client) 
     and the HOTP validator (server). 

K  shared secret between client and server; each HOTP 
     generator has a different and unique secret K. 

T  throttling parameter: the server will refuse connections 
     from a user after T unsuccessful authentication attempts. 

Тогда мы имеем алгоритм для генерации HOTP

As the output of the HMAC-SHA-1 calculation is 160 bits, we must 
    truncate this value to something that can be easily entered by a 
    user. 

        HOTP(K,C) = Truncate(HMAC-SHA-1(K,C)) 

Тогда мы Округление определяется как

String = String[0]...String[19] 
Let OffsetBits be the low-order 4 bits of String[19] 
Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15 
Let P = String[OffSet]...String[OffSet+3] 
Return the Last 31 bits of P 

А затем пример предлагается для 6 цифр HOTP

The following code example describes the extraction of a dynamic 
binary code given that hmac_result is a byte array with the HMAC- 
SHA-1 result: 

    int offset = hmac_result[19] & 0xf ; 
    int bin_code = (hmac_result[offset] & 0x7f) << 24 
     | (hmac_result[offset+1] & 0xff) << 16 
     | (hmac_result[offset+2] & 0xff) << 8 
     | (hmac_result[offset+3] & 0xff) ; 

я скорее в недоумении, пытаясь преобразовать его в полезный код C# для генерации одноразовых паролей. У меня уже есть код для создания истекающей HMAC следующим образом:

byte[] hashBytes = alg.ComputeHash(Encoding.UTF8.GetBytes(input)); 
byte[] result = new byte[8 + hashBytes.Length]; 

hashBytes.CopyTo(result, 8); 
BitConverter.GetBytes(expireDate.Ticks).CopyTo(result, 0); 

Я просто не знает, как идти от, до 6 цифр, как это предлагается в приведенных выше алгоритмах.

+0

Я считаю, что C является DateTime марка и K является секретным ключом я уже назначены на счет каждого пользователя. Что касается того, как я правильно их хэш, а затем урезать до 6 цифр, я смущен. – Josh 2010-11-29 21:02:53

+1

Приложение C предоставляет ссылочную реализацию на Java, которая должна быть легко переводимой на C#. – dtb 2010-11-29 21:06:09

+0

Это, но он генерирует только число HOTP. Мне бы очень понравился альфа-числовой HOTP. – Josh 2010-11-29 21:26:37

ответ

3

У вас есть две проблемы здесь:

  1. Если вы создаете буквенно-цифровой, вы не отвечающие требованиям в RFC - на данный момент, вы можете просто взять какие-N байтов и превратить их в шестнадцатеричном string и получить буквенно-цифровой. Или, convert them to base 36, если вы хотите a-z и 0-9. Раздел 5.4 RFC дает вам стандартную выдержку HOTP для параметра Digit (обратите внимание, что Digit является параметром вместе с C, K и T). Если вы решили игнорировать этот раздел, вам не нужно преобразовывать код - просто используйте то, что вы хотите.

  2. Ваш массив результатов «результат» имеет время истечения, просто забитое в первые 8 байтов после хэширования. Если ваше усечение до 6-значного алфавитно-цифрового номера не собирает их вместе с частями хэша, оно также не может быть рассчитано вообще. Также очень легко «подделка» или повторное воспроизведение - хеш-секрет один раз, а затем поставить любые тики, которые вы хотите перед ним, - не совсем одноразовый пароль. Обратите внимание, что параметр C в RFC предназначен для выполнения окна с истечением срока действия и должен быть добавлен во вход до, вычисляющий хэш-код.

2

Для всех, кого это интересует, я выяснил способ создания срока действия моего пароля. Подходом является использование созданного времени до минуты (без учета секунд, миллисекунд и т. Д.). Если у вас есть это значение, используйте тики DateTime в качестве вашего счетчика или переменную C.

otpLifespan - мой срок службы HOTP за считанные минуты.

DateTime current = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 
    DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0); 

for (int x = 0; x <= otpLifespan; x++) 
{ 
    var result = NumericHOTP.Validate(hotp, key, 
     current.AddMinutes(-1 * x).Ticks); 

    //return valid state if validation succeeded 

    //return invalid state if the passed in value is invalid 
    // (length, non-numeric, checksum invalid) 
} 

//return expired state 

Мой истекающий HOTP простирается от моей цифровой HOTP, который имеет статический метод проверки, который проверяет длину, обеспечивает это числовое, проверяет контрольную сумму, если он используется, и, наконец, сравнивает HOTP передается с использованием генерируемых один ,

Единственный недостаток этого в том, что каждый раз, когда вы проверяете истечение срока действия горячего адреса, ваш худший сценарий - проверка n + 1 значений HOTP, где n - продолжительность жизни в минутах.

Пример кода Java в документе с изложением RFC 4226 был очень простым движением в C#. Единственным моментом, который я действительно должен был приложить к переписанию, был метод хеширования.

private static byte[] HashHMACSHA1(byte[] keyBytes, byte[] text) 
{ 
    HMAC alg = new HMACSHA1(keyBytes); 

    return alg.ComputeHash(text); 
} 

Я надеюсь, что это поможет кому-либо еще пытаться генерировать одноразовые пароли.

2

Этот фрагмент кода должен делать то, что вы просите:

public class UniqueId 
{ 
    public static string GetUniqueKey() 
    { 
     int maxSize = 6; // whatever length you want 
     char[] chars = new char[62]; 
     string a; 
     a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; 
      char[] chars = new char[a.Length]; 
     chars = a.ToCharArray(); 
     int size = maxSize; 
     byte[] data = new byte[1]; 
     RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider(); 
     crypto.GetNonZeroBytes(data); 
     size = maxSize; 
     data = new byte[size]; 
     crypto.GetNonZeroBytes(data); 
     StringBuilder result = new StringBuilder(size); 
     foreach (byte b in data) 
     { result.Append(chars[b % (chars.Length - 1)]); } 
     return result.ToString(); 
    } 
} 
Смежные вопросы