Last active
April 17, 2024 17:28
-
-
Save thatkid02/c9d2570b9abc917f4a3bf992fc0b4ef9 to your computer and use it in GitHub Desktop.
Implementing 2FA in javascript for 101
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const crypto = require("crypto"); | |
/** | |
* Convert decimal to hexadecimal. | |
* @param {number} s - Decimal number to convert. | |
* @returns {string} Hexadecimal representation of the decimal number. | |
*/ | |
function dec2hex(s) { | |
return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); | |
} | |
/** | |
* Convert hexadecimal to decimal. | |
* @param {string} s - Hexadecimal number to convert. | |
* @returns {number} Decimal representation of the hexadecimal number. | |
*/ | |
function hex2dec(s) { | |
return parseInt(s, 16); | |
} | |
/** | |
* Convert base32 string to hexadecimal. | |
* @param {string} base32 - Base32 string to convert. | |
* @returns {string} Hexadecimal representation of the base32 string. | |
*/ | |
function base32tohex(base32) { | |
// Base32 characters mapping | |
const base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; | |
let bits = ""; | |
let hex = ""; | |
// Convert each character in base32 string to binary | |
for (let i = 0; i < base32.length; i++) { | |
const val = base32chars.indexOf(base32.charAt(i).toUpperCase()); | |
bits += leftpad(val.toString(2), 5, '0'); | |
} | |
// Convert binary to hexadecimal | |
for (let i = 0; i + 4 <= bits.length; i += 4) { | |
const chunk = bits.substr(i, 4); | |
hex = hex + parseInt(chunk, 2).toString(16); | |
} | |
return hex; | |
} | |
/** | |
* Left-pad a string with the specified character to the specified length. | |
* @param {string} str - String to pad. | |
* @param {number} len - Desired length after padding. | |
* @param {string} pad - Character used for padding. | |
* @returns {string} Left-padded string. | |
*/ | |
function leftpad(str, len, pad) { | |
if (len + 1 >= str.length) { | |
str = Array(len + 1 - str.length).join(pad) + str; | |
} | |
return str; | |
} | |
/** | |
* Generate HMAC-SHA1 hash. | |
* @param {Buffer} key - Key used for hashing. | |
* @param {Buffer} data - Data to hash. | |
* @returns {string} Hexadecimal representation of the hash. | |
*/ | |
function hmacSHA1(key, data) { | |
// Create HMAC-SHA1 hash using crypto module | |
const hashBuffer = crypto.createHmac('sha1', key).update(data).digest(); | |
// Convert hash buffer to hexadecimal string | |
return Buffer.from(hashBuffer).toString('hex'); | |
} | |
/** | |
* Generate One-Time Password (OTP) using HMAC-SHA1 algorithm. | |
* @param {string} secret - Secret key for OTP generation. | |
* @returns {string} 6-digit OTP. | |
*/ | |
function generateOTP(secret) { | |
// Convert secret from base32 to hexadecimal | |
const key = base32tohex(secret); | |
// Get current time in seconds since epoch | |
const epoch = Math.round(new Date().getTime() / 1000.0); | |
// Convert time to hexadecimal | |
const time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); | |
// Generate HMAC-SHA1 hash using key and time | |
const hmac = hmacSHA1(Buffer.from(key, 'hex'), Buffer.from(time, 'hex')); | |
// Extract offset and OTP value from HMAC hash | |
const offset = hex2dec(hmac.substring(hmac.length - 1)); | |
const otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; | |
return otp.substr(otp.length - 6, 6); | |
} | |
// Secret key for OTP generation | |
const secret = 'HMNDNFYJGHPE3PAX'; | |
// Generate and print OTP | |
console.log(generateOTP(secret)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment