Одноразовый пароль на основе HMAC в C # (RFC 4226 - HOTP) - PullRequest
5 голосов
/ 29 ноября 2010

Я пытаюсь обернуть свой мозг вокруг создания однозначного пароля с шестизначным / символьным регистром без учета регистра.

Мой источник 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))

Тогда у нас есть Truncate, определенный как

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 цифрам, как предложено в приведенных выше алгоритмах.

Ответы [ 3 ]

3 голосов
/ 30 ноября 2010

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

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

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

2 голосов
/ 01 января 2012

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

  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();
    }
}
2 голосов
/ 01 декабря 2010

Для тех, кто заинтересован, я нашел способ встроить срок действия в свой одноразовый пароль. Подход заключается в том, чтобы использовать созданное время с точностью до минуты (игнорируя секунды, миллисекунды и т. Д.). Получив это значение, используйте тики 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);
}

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

...