otp-io
Version:
🕖 Typed library to work 2fa via Google Authenticator/Time-based TOTP/Hmac-based HOTP
108 lines • 3.48 kB
JavaScript
const charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const byteTable = new Uint8Array([
0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff,
0xff, 0xff
]);
/**
*
*
* @param {Uint8Array} buffer
* @return {number}
*/
function quintetCount(buffer) {
return Math.ceil(buffer.length / 5);
}
/**
*
*
* @export
* @param {Uint8Array} plain
* @return {string} {string}
*/
function encode(plain, { pad = true } = {}) {
let index = 0;
let encodedIndex = 0;
let shiftIndex = 0;
let digit = 0;
const encoded = Array.from({ length: quintetCount(plain) * 8 });
while (index < plain.length) {
const current = plain[+index];
if (shiftIndex > 3) {
digit = current & (0xff >> shiftIndex);
shiftIndex = (shiftIndex + 5) % 8;
digit =
(digit << shiftIndex) |
((index + 1 < plain.length ? plain[index + 1] : 0) >> (8 - shiftIndex));
index++;
}
else {
digit = (current >> (8 - (shiftIndex + 5))) & 0x1f;
shiftIndex = (shiftIndex + 5) % 8;
if (shiftIndex === 0)
index++;
}
encoded[+encodedIndex] = charTable.codePointAt(digit);
encodedIndex++;
}
if (pad) {
for (index = encodedIndex; index < encoded.length; index++) {
encoded[+index] = 0x3d;
}
}
else {
encoded.splice(encodedIndex);
}
return String.fromCharCode(...encoded);
}
/**
*
*
* @export
* @param {string} encoded
* @return {Uint8Array} {Uint8Array}
*/
function decode(encoded) {
let shiftIndex = 0;
let plainDigit = 0;
let plainChar = 0;
let plainPos = 0;
const decoded = new Uint8Array(Math.ceil((encoded.length * 5) / 8));
const chars = encoded.split("").map((char) => char.charCodeAt(0));
for (let index = 0; index < encoded.length; index++) {
if (chars[+index] === 0x3d) {
break;
}
const encodedByte = chars[+index] - 0x30;
if (encodedByte < byteTable.length) {
plainDigit = byteTable[+encodedByte];
if (shiftIndex <= 3) {
shiftIndex = (shiftIndex + 5) % 8;
if (shiftIndex === 0) {
plainChar |= plainDigit;
decoded[+plainPos] = plainChar;
plainPos++;
plainChar = 0;
}
else {
plainChar |= 0xff & (plainDigit << (8 - shiftIndex));
}
}
else {
shiftIndex = (shiftIndex + 5) % 8;
plainChar |= 0xff & (plainDigit >>> shiftIndex);
decoded[+plainPos] = plainChar;
plainPos++;
plainChar = 0xff & (plainDigit << (8 - shiftIndex));
}
}
else {
throw new Error("Invalid input - it is not base32 encoded string");
}
}
return decoded.slice(0, plainPos);
}export{decode,encode};