Implementing 2FA in javascript for 101

3 min read Original article ↗
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));