C#(RFC 4226 -HOTP)のHMACベースのワンタイムパスワード
-
29-09-2019 - |
質問
私は、6桁/文字の非ケースに敏感な1回限りのパスワードを生成することに脳を包み込もうとしています。
私の情報源はそうです 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))
次に、asを定義した切り捨てを行います
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) ;
私は、これを1回限りのパスワードを生成するためにこれを有用な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桁まで行く方法がわかりません。
解決
ここには2つの問題があります。
Alpha-Numericを生成している場合は、RFCに準拠していません。この時点で、任意のnバイトを使用して16進ストリングに変換して、Alpha-Numericを取得できます。または、 それらをベース36に変換します AZと0-9が必要な場合。 RFCのセクション5.4では、セットの標準HOTP計算を提供しています
Digit
パラメーター(それに注意してくださいDigit
と一緒にパラメーターですC
,K
, 、 とT
)。このセクションを無視することを選択している場合は、コードを変換する必要はありません - 必要なものを使用してください。「結果」バイト配列の有効期限は、ハッシュ後に最初の8バイトで単純に詰め込まれています。 6桁の英数字への切り捨てがハッシュの一部とともにこれらを収集しない場合、まったく計算されない可能性があります。また、「偽物」またはリプレイすることも非常に簡単です - 秘密を一度ハッシュしてから、その前に必要なティックを置いてください - 実際には一回限りのパスワードではありません。そのパラメーターに注意してください
C
RFCでは、期限切れのウィンドウを満たすためのものであり、入力に追加する必要があります 先立って ハッシュコードの計算。
他のヒント
興味のある方のために、私は1回のパスワードに有効期限を構築する方法を見つけました。アプローチは、作成された時間を1分まで使用することです(秒、ミリ秒などを無視します)。その価値が得られたら、データタイムのティックをカウンターとして、または可変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を検証するたびに、最悪のケースシナリオは、nが数分で寿命であるn + 1のhotP値を確認することです。
RFC 4226の概要を説明するドキュメントのJavaコードの例は、C#への非常に簡単な動きでした。私が本当に書き直しに努力しなければならなかった唯一の作品は、ハッシュする方法でした。
private static byte[] HashHMACSHA1(byte[] keyBytes, byte[] text)
{
HMAC alg = new HMACSHA1(keyBytes);
return alg.ComputeHash(text);
}
これが、1回限りのパスワードを生成しようとする他の人に役立つことを願っています。
このスニペットはあなたが求めていることをするべきです:
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();
}
}