64 lines
2.1 KiB
JavaScript
64 lines
2.1 KiB
JavaScript
import { decode } from "./base32decoder.js";
|
|
import jsSHA from "jssha";
|
|
("use bigint");
|
|
/**
|
|
* get HOTP based on counter
|
|
* @param {BigInt} counter BigInt counter of HOTP
|
|
* @param {string} secret base32 encoded string
|
|
* @param {number} [digits=6] number of digits in OTP token
|
|
* @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation)
|
|
* @returns HOTP string
|
|
*/
|
|
export function getHOTP(counter, secret, digits = 6, hashType = "SHA-1") {
|
|
//Stage 1: Prepare data
|
|
const rawDataCounter = new DataView(new ArrayBuffer(8));
|
|
rawDataCounter.setUint32(4, counter);
|
|
|
|
const bCounter = new Uint8Array(rawDataCounter.buffer);
|
|
const bSecret = new Uint8Array(
|
|
decode(secret)
|
|
.match(/.{1,2}/g)
|
|
.map((chunk) => parseInt(chunk, 16)),
|
|
); //confirmed
|
|
|
|
//Stage 2: Hash data
|
|
const jssha = new jsSHA(hashType, "UINT8ARRAY");
|
|
jssha.setHMACKey(bSecret, "UINT8ARRAY");
|
|
jssha.update(bCounter);
|
|
const hmacResult = jssha.getHMAC("UINT8ARRAY"); //confirmed
|
|
|
|
//Stage 3: Dynamic truncate
|
|
const offsetB = hmacResult[19] & 0xf;
|
|
const P = hmacResult.slice(offsetB, offsetB + 4);
|
|
P[0] = P[0] & 0x7f;
|
|
|
|
//Stage 4: Format string
|
|
let res = (
|
|
new DataView(P.buffer).getInt32(0) % Math.pow(10, digits)
|
|
).toString();
|
|
while (res.length < digits) res = "0" + res;
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* get OTP based on current time
|
|
* @param {string} secret base32 encoded string
|
|
* @param {number} [digits=6] digits in OTP
|
|
* @param {number} [time=Date.now()] time for counter (default unix time epoch)
|
|
* @param {number} [fetchTime=30] period of token in seconds
|
|
* @param {number} [timeOffset=0] time offset for token in seconds
|
|
* @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation)
|
|
* @returns TOTP string
|
|
*/
|
|
export function getTOTP(
|
|
secret,
|
|
digits = 6,
|
|
time = Date.now(),
|
|
fetchTime = 30,
|
|
timeOffset = 0,
|
|
hashType = "SHA-1",
|
|
) {
|
|
const unixTime = Math.round((time / 1000n + timeOffset) / fetchTime);
|
|
return getHOTP(BigInt(unixTime), secret, digits, hashType);
|
|
}
|