Заодно пришлось написать на 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>
Комментариев нет:
Отправить комментарий