2010-11-29 18 views
5

J'essaie d'enrouler mon cerveau autour de générer un mot de passe à usage unique expirant sensible à 6 caractères/non-caractère.Mot de passe à usage unique basé sur HMAC en C# (RFC 4226 - HOTP)

Ma source est http://tools.ietf.org/html/rfc4226#section-5

D'abord la définition des paramètres

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. 

Ensuite, nous avons l'algorithme pour générer le 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)) 

Ensuite, nous avons troncation défini comme

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 

Et un exemple est offert pour un 6 chiffres 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) ; 

Je suis plutôt à une perte pour tenter de convertir en code utile C# pour générer un mot de passe de temps. J'ai déjà du code pour créer une échéance HMAC comme suit:

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); 

Je ne suis pas sûr de savoir comment aller de cela, à 6 chiffres tel que proposé dans les algorithmes ci-dessus.

+0

C est un timbre DateTime et K est la clé secrète que je l'ai déjà affecté au compte de chaque utilisateur. Quant à savoir comment je les hachage correctement, puis tronquer à 6 chiffres est l'endroit où je suis confus. – Josh

+1

L'annexe C fournit une implémentation de référence en Java qui devrait être facilement traduisible en C#. – dtb

+0

Il est, mais il génère uniquement un HOTP numérique. J'aimerais vraiment un HOTP alpha-numérique. – Josh

Répondre

3

Vous avez deux questions:

  1. Si vous générer de l'alpha-numérique, vous ne sont pas conformes à la RFC - à ce stade, vous pouvez simplement prendre des octets N et les tourner vers un hexagone chaîne et obtenir alpha-numérique. Ou, convert them to base 36 si vous voulez a-z et 0-9. La section 5.4 de la RFC vous donne le calcul standard HOTP pour un paramètre Digit (notez que Digit est un paramètre avec C, K et T). Si vous choisissez d'ignorer cette section, vous n'avez pas besoin de convertir le code - utilisez simplement ce que vous voulez. Votre tableau d'octets "résultat" a le temps d'expiration simplement bourré dans les 8 premiers octets après le hachage. Si votre troncature à 6 chiffres alphanumériques ne les collecte pas avec des parties du hachage, il peut également ne pas être calculé du tout. Il est également très facile de "simuler" ou de rejouer - de hacher le secret une fois, puis de mettre les tics que vous voulez en face - pas vraiment un mot de passe à usage unique. Notez que le paramètre C dans la RFC est destiné à remplir la fenêtre d'expiration et doit être ajouté à l'entrée avant de en calculant le code de hachage.

+0

Après y avoir creusé plus, j'ai dû laisser tomber l'idée d'un OTP expirant tout en un. L'algorithme utilise un compteur, c'est-à-dire qu'il a été conçu pour fonctionner indépendamment sur un périphérique matériel (comme un dongle/téléphone portable) et sur un serveur de validation. De cette façon, si le compteur est le même, le même OTP est toujours généré - car il n'y a aucun moyen de retirer les données, comme c'était le cas dans mon HMAC expirant. Mais vous avez raison sur les deux points. Pour le faire expirer je dois faire plus que ce que l'algorithme est configuré, et si je veux avoir un OTP alphanumérique alors j'ai besoin d'un algorithme différent. – Josh

2

Pour ceux qui sont intéressés, j'ai trouvé un moyen de construire l'expiration de mon mot de passe à usage unique. L'approche consiste à utiliser l'heure créée à la minute (en ignorant les secondes, les millisecondes, etc.). Une fois que vous avez cette valeur, utilisez les ticks du DateTime comme votre compteur, ou la variable C.

otpLifespan est ma durée de vie HOTP en minutes.

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 

Mon expiration HOTP va de mon HOTP numérique qui a une méthode de validation statique qui contrôle la longueur, assure qu'il est numérique, valide la somme de contrôle si elle est utilisée, et compare enfin le HOTP passé avec un produit un .Le seul inconvénient de ceci est que chaque fois que vous validez un hotp expirant, votre pire scénario est de vérifier n + 1 valeurs HOTP où n est la durée de vie en minutes.

L'exemple de code java dans le document décrivant la RFC 4226 était un mouvement très simple en C#. La seule chose que j'ai vraiment dû faire pour réécrire était la méthode de hachage. J'espère que cela aidera tous ceux qui tentent de générer des mots de passe à usage unique.

2

Cet extrait doit faire ce que vous demandez:

je crois
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(); 
    } 
} 
+0

Ça a l'air bien. Mais 'char [] chars = new char [a.Length];' donne une erreur car les caractères ont déjà été définis dans la portée. –