Заодно пришлось написать на Javascript функции для SHA1 и HMAC-SHA1. В общем, бесполезный, но интересный опыт.
Текст программки:
<html>
<script language="Javascript">
////////////////////////////////////////////////////////////////////////////////////////////////////
// поле "Логин"
var login = "";
// если пустое, будем искать это значение в поле ключа
// поле "Ключ д/ф аутентификации"
var token = "otpauth://totp/Google:SomeUser?secret=KNXW2ZKTMVRXEZLU&issuer=Google";
// может быть в виде:
// otpauth://totp/XXX?secret=XXX&issuer=XXX
// secret=XXXX
// в явном виде XxXx
////////////////////////////////////////////////////////////////////////////////////////////////////
</script>
<head>
<title>Pin-код для двухфакторной аутентификации</title>
<HTA:APPLICATION
APPLICATIONNAME="Google_Auth"
maximizeButton="no"
minimizeButton="no"
sysMenu="yes"
contextMenu="yes"
scroll="no"
></HTA:APPLICATION>
</head>
<body onload="self.window_onLoad()">
<table width="100%" style="color:darkgray">
<tr>
<td width="50%"><span id="loginText"></span></td>
<td width="50%" align="right"><span id="tokenText"></span></td>
</tr>
</table>
<p align="center">Код аутентификатора:<br><span id="pin" align="center" style="font-size:30px;font-weight:bold">------</span></p>
<table width="100%">
<tr><td colspan="2" align="center">Текущее время UTC</td></tr>
<tr><td>Ваш компьютер:</td><td><span id="localDT"></span></td></tr>
<tr><td>Общемировое:</td><td><span id="globalDT"></span></td></tr>
</table>
<p align="center"><button onclick="self.close()">Выход</button></p>
</body>
<script language="Javascript">
var
IntervalLength = 30,
PinTemplate = "000000",
PinModulo = Math.pow(10, PinTemplate.length),
sizeOfInt32 = 4;
var globalTimeDiff = 0,
globalTimeChecked = false,
xhr = new XMLHttpRequest(),
previousInterval = 0;
function window_onLoad()
{
self.resizeTo (350, 290);
document.getElementById("loginText").innerText = getLoginValue();
document.getElementById("tokenText").innerText = getTokenValue();
self.localDate();
self.setInterval (self.onTimer, 1000);
self.globalDate();
}
function localDate()
{
var dt = new Date(),
D = dt.getUTCDate(),
M = dt.getUTCMonth()+1,
Y = dt.getUTCFullYear(),
H = dt.getUTCHours(),
m = dt.getUTCMinutes(),
S = dt.getUTCSeconds(),
offset = dt.getTimezoneOffset();
dt.setSeconds(S + globalTimeDiff);
var
gD = dt.getUTCDate(),
gM = dt.getUTCMonth()+1,
gY = dt.getUTCFullYear(),
gH = dt.getUTCHours(),
gm = dt.getUTCMinutes(),
gS = dt.getUTCSeconds();
document.getElementById("localDT").innerText = ""
+ ((D < 10) ? "0" : "") + D
+ "." + ((M < 10) ? "0" : "") + M
+ "." + Y
+ " " + ((H < 10) ? "0" : "") + H
+ ":" + ((m < 10) ? "0" : "") + m
+ ":" + ((S < 10) ? "0" : "") + S;
//+ " (" + (offset/60) + ")";
document.getElementById("globalDT").innerText = globalTimeChecked ? ""
+ ((gD < 10) ? "0" : "") + gD
+ "." + ((gM < 10) ? "0" : "") + gM
+ "." + gY
+ " " + ((gH < 10) ? "0" : "") + gH
+ ":" + ((gm < 10) ? "0" : "") + gm
+ ":" + ((gS < 10) ? "0" : "") + gS : "----";
}
function globalDate()
{
xhr.onreadystatechange = function ()
{
if (xhr.readyState === 4)
{
if (xhr.status === 200)
{
var dt = new Date();
globalTimeDiff = 1*xhr.responseText - dt.getTime()/1000;
globalTimeChecked = true;
}
}
};
xhr.open("GET", "http://time.xdy-ydx.ru/text", true);
xhr.send(null);
}
function onTimer()
{
localDate();
generatePin();
}
function setPinCodeText(text)
{
var e = document.getElementById("pin");
if (e.innerText != text)
e.innerText = text;
}
function getLoginValue()
{
if (login != "")
return login;
if (token.substring(0, 15) == "otpauth://totp/")
{
var vals = token.substring(15).split("?");
return vals[0];
}
return "";
}
function getTokenValue()
{
if (token.substring(0, 15) == "otpauth://totp/")
{
var vals = token.substring(15).split(/\?|\&/);
for (var i = 0; i < vals.length; i++)
if (vals[i].substring(0, 7) == "secret=")
return Base32_Decode(vals[i].substring(7));
}
if (token.substring(0, 7) == "secret=")
return Base32_Decode(token.substring(7));
return token;
}
// thanks to
// http://stackoverflow.com/questions/6421950/is-there-a-tutorial-on-how-to-implement-google-authenticator-in-net-apps
function generatePin()
{
var dt = new Date();
var interval = Math.floor(dt.getTime()/(1000*IntervalLength));
if (interval == previousInterval)
return;
previousInterval = interval;
var CounterBytes = pushInt64ToByteArray([], interval);
var tokenValue = getTokenValue();
var key = [];
for (var i = 0; i < tokenValue.length; i++)
key.push(tokenValue.charCodeAt(i));
var hash = HMAC(SHA1, key, CounterBytes);
var offset = hash[hash.length - 1] & 0xF;
var selectedBytes = hash.slice(offset, offset + sizeOfInt32);
var selectedInteger =
(selectedBytes[0] << 24) +
(selectedBytes[1] << 16) +
(selectedBytes[2] << 8) +
selectedBytes[3];
var truncatedHash = selectedInteger & 0x7FFFFFFF;
var PinNumber = "" + truncatedHash % PinModulo;
var Pin = (PinTemplate + PinNumber).substring(PinTemplate.length + PinNumber.length - 6);
setPinCodeText( Pin );
}
function pushInt32ToByteArray (arr, N)
{
arr.push( (N >>> 24) & 0xFF );
arr.push( (N >>> 16) & 0xFF );
arr.push( (N >>> 8) & 0xFF );
arr.push( (N >>> 0) & 0xFF );
return arr;
}
function pushInt64ToByteArray (arr, N)
{
var lo = N & 0xFFFFFFFF,
hi = (N - lo) / 0x100000000;
arr = pushInt32ToByteArray (arr, hi);
arr = pushInt32ToByteArray (arr, lo);
return arr;
}
function byteArrayToString (bytes)
{
var s = "";
for (var i = 0; i < bytes.length; i++)
s += ((i == 0) ? "" : " ") + bytes[i].toString(16);
return s;
}
function SHA1 (bytes)
{
var shl = function (x, y)
{
return ( (x << y) + (x >>> (32-y)) );
};
var
h0 = 0x67452301,
h1 = 0xEFCDAB89,
h2 = 0x98BADCFE,
h3 = 0x10325476,
h4 = 0xC3D2E1F0;
buf = [];
count = 0;
for (var i = 0; i < bytes.length; i++)
{
buf.push(bytes[i]);
count += 8; if (count == 512) count = 0;
}
buf.push(0x80);
count += 8; if (count == 512) count = 0;
while (count != 448)
{
buf.push(0);
count += 8; if (count == 512) count = 0;
}
buf = pushInt64ToByteArray(buf, bytes.length*8);
for (var i = 0; i < buf.length; i += 64)
{
var w = [];
for (var j = 0; j < 16; j++)
w.push( (buf[i+j*4] << 24) + (buf[i+j*4+1] << 16) + (buf[i+j*4+2] << 8) + buf[i+j*4+3] );
for (var j = 16; j < 80; j++)
w.push( shl(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1) );
var
a = h0,
b = h1,
c = h2,
d = h3,
e = h4,
f = 0,
k = 0,
x;
for (var j = 0; j < 80; j++)
{
if (j < 20)
{
f = (b & c) | ((~b) & d);
k = 0x5A827999;
}
else
if (j < 40)
{
f = b ^ c ^ d;
k = 0x6ED9EBA1;
}
else
if (j < 60)
{
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
}
else
{
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
x = ( shl(a, 5) + f + e + k + w[j]) & 0xFFFFFFFF;
e = d;
d = c;
c = shl(b, 30) & 0xFFFFFFFF;
b = a;
a = x;
}
h0 = (h0 + a) & 0xFFFFFFFF;
h1 = (h1 + b) & 0xFFFFFFFF;
h2 = (h2 + c) & 0xFFFFFFFF;
h3 = (h3 + d) & 0xFFFFFFFF;
h4 = (h4 + e) & 0xFFFFFFFF;
}
var result = [];
result = pushInt32ToByteArray(result, h0);
result = pushInt32ToByteArray(result, h1);
result = pushInt32ToByteArray(result, h2);
result = pushInt32ToByteArray(result, h3);
result = pushInt32ToByteArray(result, h4);
return (result);
}
function HMAC (H, key, msg)
{
var
K0 = [],
count = 0;
for (var i = 0; i < key.length; i++)
{
K0.push(key[i]);
count++;
}
while (count < 64)
{
K0.push(0);
count++;
}
var chunk1 = [];
for (var i = 0; i < K0.length; i++)
chunk1.push(K0[i] ^ 0x36);
for (var i = 0; i < msg.length; i++)
chunk1.push(msg[i]);
var hash1 = H(chunk1);
var chunk2 = [];
for (var i = 0; i < K0.length; i++)
chunk2.push(K0[i] ^ 0x5c);
for (var i = 0; i < hash1.length; i++)
chunk2.push(hash1[i]);
return H(chunk2);
}
// thanks to http://stackoverflow.com/questions/641361/base32-decoding
function Base32_Decode (key)
{
var charToValue = function (value)
{
//65-90 == uppercase letters
if (value < 91 && value > 64)
return (value - 65);
//50-55 == numbers 2-7
if (value < 56 && value > 49)
return (value - 24);
//97-122 == lowercase letters
if (value < 123 && value > 96)
return (value - 97);
return 0;
}
while(key.charAt(key.length-1) == "=")
key = key.substring(0, key.length-1);
var result = "";
var
curByte = 0,
bitsRemaining = 8;
mask = 0;
for (var i = 0; i < key.length; i++)
{
var cValue = charToValue(key.charCodeAt(i));
if (bitsRemaining > 5)
{
mask = cValue << (bitsRemaining - 5);
curByte = (curByte | mask) & 0xFF;
bitsRemaining -= 5;
}
else
{
mask = cValue >>> (5 - bitsRemaining);
curByte = (curByte | mask) & 0xFF;
result += String.fromCharCode(curByte);
curByte = (cValue << (3 + bitsRemaining)) & 0xFF;
bitsRemaining += 3;
}
}
if ( ((key.length * 5 / 8)|0) != result.length )
result += String.fromCharCode(curByte);
return result;
}
</script>
</html>
Комментариев нет:
Отправить комментарий