You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
7.2 KiB

/*
* Standalone TOTP implementation in Ecmascript.
*
* http://tools.ietf.org/html/rfc6238
* http://tools.ietf.org/html/rfc4226
* http://tools.ietf.org/html/rfc3174
* http://tools.ietf.org/html/rfc2104
* http://tools.ietf.org/html/rfc3548
*
* ">>> 0" is used to coerce result to unsigned 32 bits.
*
* Exercises bit arithmetic.
*/
var TOTP = {};
TOTP.nybbles = "0123456789abcdef";
TOTP.eightByteDivisors = [ 72057594037927936, 281474976710656, 1099511627776,
4294967296, 16777216, 65536, 256, 1 ];
TOTP.fiveByteDivisors = [ 4294967296, 16777216, 65536, 256, 1 ];
TOTP.stringToBytes = function (val) {
var i, n, res;
for (i = 0, n = val.length, res = []; i < n; i++) {
res.push(val.charCodeAt(i) & 0xff);
}
return res;
};
TOTP.bytesToString = function (val) {
return String.fromCharCode.apply(String, val);
};
TOTP.hexEncode = function (val) {
var i, n, t, res, nybbles = TOTP.nybbles;
for (i = 0, n = val.length, res = ''; i < n; i++) {
t = (val.charCodeAt(i) & 0xff);
res += nybbles[t >> 4] + nybbles[t & 0x0f];
}
return res;
};
TOTP.base32Decode = function (val) {
// The authenticators given are not direct inputs to TOTP, but are base-32 encoded.
// http://stackoverflow.com/questions/8529265/google-authenticator-implementation-in-python
// https://docs.python.org/3/library/base64.html#base64.b32decode
var i, j, n, x, t, npad, res, nstrip;
while ((val.length % 8) !== 0) { val += '='; } // tolerate missing pad
for (i = 0, n = val.length, t = 0, npad = 0, res = ''; i < n; i++) {
x = val.charCodeAt(i);
if (x >= 0x41 && x <= 0x5a) { t = t * 32 + (x - 0x41); }
else if (x >= 0x61 && x <= 0x7a) { t = t * 32 + (x - 0x61); }
else if (x >= 0x32 && x <= 0x37) { t = t * 32 + (x - 0x32 + 26); }
else if (x == 0x3d /* = */) { t = t * 32; npad++; }
else { throw new Error('invalid base32'); }
if ((i % 8) == 7) {
TOTP.fiveByteDivisors.forEach(function (divisor) {
res += String.fromCharCode((t / divisor) & 0xff);
});
t = 0;
}
}
nstrip = [ 0, 1, undefined, 2, 3, undefined, 4][npad];
if (nstrip === undefined) { throw new Error('invalid padding'); }
return res.substring(0, res.length - nstrip);
};
TOTP.sha1Raw = function (words) {
var w0_idx, w;
var h0, h1, h2, h3, h4, a, b, c, d, e;
var t, temp;
var Kval = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ];
var res;
function S(x, n) { return ((x << n) | (x >>> (32 - n))) >>> 0; }
function K(t) { return Kval[Math.floor(t / 20)]; }
function f(t, b, c, d) {
if (t < 20) { return ((b & c) | ((~b) & d)) >>> 0; }
else if (t < 40) { return (b ^ c ^ d) >>> 0; }
else if (t < 60) { return ((b & c) | (b & d) | (c & d)) >>> 0; }
else { return (b ^ c ^ d) >>> 0; }
}
// 'words' is an array of 32-bit values, padded to a multiple of 16 entries
if ((words.length % 16) !== 0) {
throw new Error('words length must be a multiple of 16');
}
h0 = 0x67452301; h1 = 0xefcdab89; h2 = 0x98badcfe; h3 = 0x10325476; h4 = 0xc3d2e1f0;
for (w0_idx = 0; w0_idx < words.length; w0_idx += 16) {
for (w = [], t = 0; t < 16; t++) { w[t] = words[w0_idx + t]; }
for (t = 16; t < 80; t++) {
w[t] = S((w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]) >>> 0, 1);
}
a = h0; b = h1; c = h2; d = h3; e = h4;
for (t = 0; t < 80; t++) {
temp = (S(a, 5) + f(t, b, c, d) + e + w[t] + K(t)) >>> 0;
e = d; d = c; c = S(b, 30); b = a; a = temp;
}
h0 = (h0 + a) >>> 0; h1 = (h1 + b) >>> 0; h2 = (h2 + c) >>> 0;
h3 = (h3 + d) >>> 0; h4 = (h4 + e) >>> 0;
}
res = ''; [ h0, h1, h2, h3, h4 ].forEach(function (v) {
for (var i = 24; i >= 0; i -= 8) { res += String.fromCharCode((v >> i) & 0xff); }
});
return res;
};
TOTP.sha1 = function (str) {
var bytes, words, i, n;
var bitlen = str.length * 8; // assume representable with 53 bits
bytes = TOTP.stringToBytes(str);
bytes.push(0x80);
while (bytes.length % 64 !== 56) { bytes.push(0x00); }
TOTP.eightByteDivisors.forEach(function (divisor) {
bytes.push((bitlen / divisor) & 0xff);
});
//print('sha1 bytes', TOTP.hexEncode(TOTP.bytesToString(bytes)));
for (i = 0, n = bytes.length, words = []; i < n; i += 4) {
words[i / 4] = ((bytes[i] << 24) | (bytes[i + 1] << 16) |
(bytes[i + 2] << 8) | bytes[i + 3]) >>> 0;
}
//words.forEach(function (v) { print((v >>> 0).toString(16)); });
return TOTP.sha1Raw(words);
};
TOTP.hmacSha1 = function (key, text) {
var L = 20, B = 64;
var i, n, t1, t2;
if (key.length > B) { throw new Error('key too long (key hashing not supported)'); }
for (i = 0, n = key.length, t1 = '', t2 = ''; i < B; i++) {
t1 += String.fromCharCode(((key.charCodeAt(i) || 0) ^ 0x36) & 0xff);
t2 += String.fromCharCode(((key.charCodeAt(i) || 0) ^ 0x5c) & 0xff);
}
return TOTP.sha1(t2 + TOTP.sha1(t1 + text));
};
TOTP.HOTP = function (K, C, digits) {
var Cstr = '';
var HS, off, i, t, res;
TOTP.eightByteDivisors.forEach(function (divisor) {
Cstr += String.fromCharCode((C / divisor) & 0xff);
});
HS = TOTP.hmacSha1(K, Cstr);
off = HS.charCodeAt(19) & 0x0f;
for (i = off, t = 0; i < off + 4; i++) {
t = (t << 8) + (HS.charCodeAt(i) & 0xff);
}
t &= 0x7fffffff;
res = '';
while (digits-- > 0) { res = Math.floor(t % 10) + res; t = Math.floor(t / 10); }
return res;
};
TOTP.TOTP = function (K, time, digits) {
// Assume T0 = 0 (Unix epoch), X = 30 seconds, time is in milliseconds
var T = Math.floor(time / 30e3);
return TOTP.HOTP(K, T, digits);
};
/*===
sha1
da39a3ee5e6b4b0d3255bfef95601890afd80709
8843d7f92416211de9ebb963ff4ce28125932878
hmac-sha1
effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
hotp
755224
287082
359152
969429
338314
254676
287922
162583
399871
520489
totp
94287082
07081804
14050471
65353130
base32
f
f
fo
fo
foo
foo
foo
foo
foo b
foo bar
foo bar
===*/
function test() {
var i;
print('sha1');
print(TOTP.hexEncode(TOTP.sha1('')));
print(TOTP.hexEncode(TOTP.sha1('foobar')));
print('hmac-sha1');
print(TOTP.hexEncode(TOTP.hmacSha1('Jefe', 'what do ya want for nothing?')));
print('hotp');
for (i = 0; i < 10; i++) {
print(TOTP.HOTP('12345678901234567890', i, 6));
}
print('totp');
print(TOTP.TOTP('12345678901234567890', 59e3, 8));
print(TOTP.TOTP('12345678901234567890', 1111111109e3, 8));
print(TOTP.TOTP('12345678901234567890', 1111111111e3, 8));
print(TOTP.TOTP('12345678901234567890', 20000000000e3, 8));
print('base32');
print(TOTP.base32Decode('MY======'));
print(TOTP.base32Decode('MY'));
print(TOTP.base32Decode('MZXQ===='));
print(TOTP.base32Decode('MZXQ'));
print(TOTP.base32Decode('MZXW6==='));
print(TOTP.base32Decode('MZXW6'));
print(TOTP.base32Decode('MZXW6IA='));
print(TOTP.base32Decode('MZXW6IA'));
print(TOTP.base32Decode('MZXW6IDC'));
print(TOTP.base32Decode('MZXW6IDCMFZA===='));
print(TOTP.base32Decode('MZXW6IDCMFZA'));
}
try {
test();
} catch (e) {
print(e);
}