我试图围绕生成一个6位/字符非大小写敏感的一次性密码到期。
我的来源是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位数。
答案 0 :(得分:3)
这里有两个问题:
如果您正在生成字母数字,那么您就不符合RFC - 此时,您可以简单地取任何N个字节并将它们转换为十六进制字符串并获得字母数字。或者,convert them to base 36如果你想要a-z和0-9。 RFC的第5.4节为您提供了一组Digit
参数的标准HOTP计算(请注意Digit
是一个参数以及C
,K
和{{1 }})。如果您选择忽略此部分,则无需转换代码 - 只需使用您想要的内容。
您的“结果”字节数组在散列后的前8个字节中只填充了到期时间。如果您截断到6位字母数字不会将这些与哈希的部分一起收集,那么根本不能计算它。它也很容易“伪造”或重放 - 将秘密哈希一次,然后在你面前放置你想要的任何刻度 - 而不是真正的一次性密码。请注意,RFC中的参数T
旨在满足过期窗口,应在计算哈希码之前添加到输入。
答案 1 :(得分: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与生成的hotp进行比较。
唯一的缺点是,每次验证即将到期的hotp时,最糟糕的情况是检查n + 1个HOTP值,其中n是以分钟为单位的寿命。
描述RFC 4226的文档中的Java代码示例是一个非常直接的C#进展。我唯一需要努力重写的部分是散列方法。
private static byte[] HashHMACSHA1(byte[] keyBytes, byte[] text)
{
HMAC alg = new HMACSHA1(keyBytes);
return alg.ComputeHash(text);
}
我希望这可以帮助其他人尝试生成一次性密码。
答案 2 :(得分: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();
}
}