[hotp] Was not giving right code...(So I Maintained for 2021)
suprim12 opened this issue · 2 comments
suprim12 commented
- Remove console.log
- Use as it is
- Copy Code added it to file, use same as speakeeasy (tested encoding ascii with google authenticator)
'use strict';
var base32 = require('base32.js');
var crypto = require('crypto');
var url = require('url');
var util = require('util');
exports.digest = function digest (options) {
var i;
// unpack options
var secret = options.secret;
var counter = options.counter;
var encoding = options.encoding || 'ascii';
var algorithm = (options.algorithm || 'sha1').toLowerCase();
// Backwards compatibility - deprecated
if (options.key != null) {
console.warn('Speakeasy - Deprecation Notice - Specifying the secret using `key` is no longer supported. Use `secret` instead.');
secret = options.key;
}
// convert secret to buffer
if (!Buffer.isBuffer(secret)) {
if (encoding === 'base32') { secret = base32.decode(secret); }
secret = Buffer.from(secret, encoding);
}
var secret_buffer_size;
if (algorithm === 'sha1') {
secret_buffer_size = 20; // 20 bytes
} else if (algorithm === 'sha256') {
secret_buffer_size = 32; // 32 bytes
} else if (algorithm === 'sha512') {
secret_buffer_size = 64; // 64 bytes
} else {
console.warn('Speakeasy - The algorithm provided (`' + algorithm + '`) is not officially supported, results may be different than expected.');
}
// The secret for sha1, sha256 and sha512 needs to be a fixed number of bytes for the one-time-password to be calculated correctly
// Pad the buffer to the correct size be repeating the secret to the desired length
if (secret_buffer_size && secret.length !== secret_buffer_size) {
secret = Buffer.from(Array(Math.ceil(secret_buffer_size / secret.length) + 1).join(secret.toString('hex')), 'hex').slice(0, secret_buffer_size);
}
// create an buffer from the counter
var buf = Buffer.alloc(8);
var tmp = counter;
for (i = 0; i < 8; i++) {
// mask 0xff over number to get last 8
buf[7 - i] = tmp & 0xff;
// shift 8 and get ready to loop over the next batch of 8
tmp = tmp >> 8;
}
// init hmac with the key
var hmac = crypto.createHmac(algorithm, secret);
// update hmac with the counter
hmac.update(buf);
// return the digest
return hmac.digest();
};
exports.hotp = function hotpGenerate (options) {
// verify secret and counter exists
var secret = options.secret;
var key = options.key;
var counter = options.counter || 0;
if (key === null || typeof key === 'undefined') {
if (secret === null || typeof secret === 'undefined') {
throw new Error('Speakeasy - hotp - Missing secret');
}
}
if (counter === null || typeof counter === 'undefined') {
throw new Error('Speakeasy - hotp - Missing counter');
}
// unpack digits
// backward compatibility: `length` is also accepted here, but deprecated
var digits = (options.digits != null ? options.digits : options.length) || 6;
if (options.length != null)
console.warn('Speakeasy - Deprecation Notice - Specifying token digits using `length` is no longer supported. Use `digits` instead.');
// digest the options
var digest = options.digest || exports.digest(options);
// compute HOTP offset
var offset = digest[digest.length - 1] & 0xf;
// calculate binary code (RFC4226 5.4)
var code = (digest[offset] & 0x7f) << 24 |
(digest[offset + 1] & 0xff) << 16 |
(digest[offset + 2] & 0xff) << 8 |
(digest[offset + 3] & 0xff);
// left-pad code
code = new Array(digits + 1).join('0') + code.toString(10);
console.log(code.substr(-digits));
// return length number off digits
return code.substr(-digits);
};
function intToBytes(num) {
var bytes = [];
for(var i=7 ; i>=0 ; --i) {
bytes[i] = num & (255);
num = num >> 8;
}
return bytes;
}
function hexToBytes(hex) {
var bytes = [];
for(var c = 0, C = hex.length; c < C; c += 2) {
bytes.push(parseInt(hex.substr(c, 2), 16));
}
return bytes;
}
exports.customhotp = function customgen(options){
var key = options.secret || '';
var counter = options.counter || 0;
var p = 6;
// Create the byte array
var b = Buffer.from(intToBytes(counter));
var hmac = crypto.createHmac('sha1', Buffer.from(key));
// Update the HMAC with the byte array
var digest = hmac.update(b).digest('hex');
// Get byte array
var h = hexToBytes(digest);
// Truncate
var offset = h[19] & 0xf;
var v = (h[offset] & 0x7f) << 24 |
(h[offset + 1] & 0xff) << 16 |
(h[offset + 2] & 0xff) << 8 |
(h[offset + 3] & 0xff);
v = (v % 1000000) + '';
console.log(Array(7-v.length).join('0') + v);
return Array(7-v.length).join('0') + v;
}
// Alias counter() for hotp()
exports.counter = exports.hotp;
exports.hotp.verifyDelta = function hotpVerifyDelta (options) {
var i;
// shadow options
options = Object.create(options);
// verify secret and token exist
var secret = options.secret;
var token = options.token;
if (secret === null || typeof secret === 'undefined') throw new Error('Speakeasy - hotp.verifyDelta - Missing secret');
if (token === null || typeof token === 'undefined') throw new Error('Speakeasy - hotp.verifyDelta - Missing token');
// unpack options
var token = String(options.token);
var digits = parseInt(options.digits, 10) || 6;
var window = parseInt(options.window, 10) || 0;
var counter = parseInt(options.counter, 10) || 0;
// fail if token is not of correct length
if (token.length !== digits) {
return;
}
// parse token to integer
token = parseInt(token, 10);
// fail if token is NA
if (isNaN(token)) {
return;
}
// loop from C to C + W inclusive
for (i = counter - window; i <= counter + window; ++i) {
options.counter = i;
// domain-specific constant-time comparison for integer codes
console.log(token);
// WAS NOT GIVING RIGHT CODE -- FIXED
// if (parseInt(exports.hotp(options), 10) === token) {
// // found a matching code, return delta
// console.log(token);
// return {delta: i - counter};
// }
if (parseInt(exports.customhotp(options)) === token) {
// found a matching code, return delta
console.log(token);
return {delta: i - counter};
}
}
// no codes have matched
return null;
};
exports.hotp.verify = function hotpVerify (options) {
return exports.hotp.verifyDelta(options) != null;
};
exports._counter = function _counter (options) {
var step = options.step || 30;
var time = options.time != null ? (options.time * 1000) : Date.now();
// also accepts 'initial_time', but deprecated
var epoch = (options.epoch != null ? (options.epoch * 1000) : (options.initial_time * 1000)) || 0;
if (options.initial_time != null) console.warn('Speakeasy - Deprecation Notice - Specifying the epoch using `initial_time` is no longer supported. Use `epoch` instead.');
return Math.floor((time - epoch) / step / 1000);
};
exports.totp = function totpGenerate (options) {
// shadow options
options = Object.create(options);
// verify secret exists if key is not specified
var key = options.key;
var secret = options.secret;
if (key === null || typeof key === 'undefined') {
if (secret === null || typeof secret === 'undefined') {
throw new Error('Speakeasy - totp - Missing secret');
}
}
// calculate default counter value
if (options.counter == null) options.counter = exports._counter(options);
// pass to hotp
return this.hotp(options);
};
// Alias time() for totp()
exports.time = exports.totp;
exports.totp.verifyDelta = function totpVerifyDelta (options) {
// shadow options
options = Object.create(options);
// verify secret and token exist
var secret = options.secret;
var token = options.token;
if (secret === null || typeof secret === 'undefined') throw new Error('Speakeasy - totp.verifyDelta - Missing secret');
if (token === null || typeof token === 'undefined') throw new Error('Speakeasy - totp.verifyDelta - Missing token');
// unpack options
var window = parseInt(options.window, 10) || 0;
// calculate default counter value
if (options.counter == null) options.counter = exports._counter(options);
// adjust for two-sided window
options.counter -= window;
options.window += window;
// pass to hotp.verifyDelta
var delta = exports.hotp.verifyDelta(options);
console.log(delta);
// adjust for two-sided window
if (delta) {
delta.delta -= window;
}
return delta;
};
exports.totp.verify = function totpVerify (options) {
return exports.totp.verifyDelta(options) != null;
};
exports.generateSecret = function generateSecret (options) {
// options
if (!options) options = {};
var length = options.length || 32;
var name = options.name || 'SecretKey';
var qr_codes = options.qr_codes || false;
var google_auth_qr = options.google_auth_qr || false;
var otpauth_url = options.otpauth_url != null ? options.otpauth_url : true;
var symbols = true;
var issuer = options.issuer;
// turn off symbols only when explicity told to
if (options.symbols !== undefined && options.symbols === false) {
symbols = false;
}
// generate an ascii key
var key = this.generateSecretASCII(length, symbols);
// return a SecretKey with ascii, hex, and base32
var SecretKey = {};
SecretKey.ascii = key;
SecretKey.hex = Buffer.from(key, 'ascii').toString('hex');
SecretKey.base32 = base32.encode(Buffer.from(key)).toString().replace(/=/g, '');
// generate some qr codes if requested
if (qr_codes) {
console.warn('Speakeasy - Deprecation Notice - generateSecret() QR codes are deprecated and no longer supported. Please use your own QR code implementation.');
SecretKey.qr_code_ascii = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl=' + encodeURIComponent(SecretKey.ascii);
SecretKey.qr_code_hex = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl=' + encodeURIComponent(SecretKey.hex);
SecretKey.qr_code_base32 = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl=' + encodeURIComponent(SecretKey.base32);
}
// add in the Google Authenticator-compatible otpauth URL
if (otpauth_url) {
SecretKey.otpauth_url = exports.otpauthURL({
secret: SecretKey.ascii,
label: name,
issuer: issuer
});
}
// generate a QR code for use in Google Authenticator if requested
if (google_auth_qr) {
console.warn('Speakeasy - Deprecation Notice - generateSecret() Google Auth QR code is deprecated and no longer supported. Please use your own QR code implementation.');
SecretKey.google_auth_qr = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl=' + encodeURIComponent(exports.otpauthURL({ secret: SecretKey.base32, label: name }));
}
return SecretKey;
};
exports.generate_key = util.deprecate(function (options) {
return exports.generateSecret(options);
}, 'Speakeasy - Deprecation Notice - `generate_key()` is depreciated, please use `generateSecret()` instead.');
exports.generateSecretASCII = function generateSecretASCII (length, symbols) {
var bytes = crypto.randomBytes(length || 32);
var set = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';
if (symbols) {
set += '!@#$%^&*()<>?/[]{},.:;';
}
var output = '';
for (var i = 0, l = bytes.length; i < l; i++) {
output += set[Math.floor(bytes[i] / 255.0 * (set.length - 1))];
}
return output;
};
// Backwards compatibility - generate_key_ascii is deprecated
exports.generate_key_ascii = util.deprecate(function (length, symbols) {
return exports.generateSecretASCII(length, symbols);
}, 'Speakeasy - Deprecation Notice - `generate_key_ascii()` is depreciated, please use `generateSecretASCII()` instead.');
exports.otpauthURL = function otpauthURL (options) {
// unpack options
var secret = options.secret;
var label = options.label;
var issuer = options.issuer;
var type = (options.type || 'totp').toLowerCase();
var counter = options.counter;
var algorithm = (options.algorithm || 'sha1').toLowerCase();
var digits = options.digits || 6;
var period = options.period || 30;
var encoding = options.encoding || 'ascii';
// validate type
switch (type) {
case 'totp':
case 'hotp':
break;
default:
throw new Error('Speakeasy - otpauthURL - Invalid type `' + type + '`; must be `hotp` or `totp`');
}
// validate required options
if (!secret) throw new Error('Speakeasy - otpauthURL - Missing secret');
if (!label) throw new Error('Speakeasy - otpauthURL - Missing label');
// require counter for HOTP
if (type === 'hotp' && (counter === null || typeof counter === 'undefined')) {
throw new Error('Speakeasy - otpauthURL - Missing counter value for HOTP');
}
// convert secret to base32
if (encoding !== 'base32') secret = new Buffer.from(secret, encoding);
if (Buffer.isBuffer(secret)) secret = base32.encode(secret);
// build query while validating
var query = {secret: secret};
if (issuer) query.issuer = issuer;
if (type === 'hotp') {
query.counter = counter;
}
// validate algorithm
if (algorithm != null) {
switch (algorithm.toUpperCase()) {
case 'SHA1':
case 'SHA256':
case 'SHA512':
break;
default:
console.warn('Speakeasy - otpauthURL - Warning - Algorithm generally should be SHA1, SHA256, or SHA512');
}
query.algorithm = algorithm.toUpperCase();
}
// validate digits
if (digits != null) {
if (isNaN(digits)) {
throw new Error('Speakeasy - otpauthURL - Invalid digits `' + digits + '`');
} else {
switch (parseInt(digits, 10)) {
case 6:
case 8:
break;
default:
console.warn('Speakeasy - otpauthURL - Warning - Digits generally should be either 6 or 8');
}
}
query.digits = digits;
}
// validate period
if (period != null) {
period = parseInt(period, 10);
if (~~period !== period) {
throw new Error('Speakeasy - otpauthURL - Invalid period `' + period + '`');
}
query.period = period;
}
// return url
return url.format({
protocol: 'otpauth',
slashes: true,
hostname: type,
pathname: encodeURIComponent(label),
query: query
});
};
Basic Usage
const twofa = require('./utils/2fa');
const qrcode = require('qrcode');
const secret = twofa.generateSecret({
name:'Mero'
})
qrcode.toDataURL(secret.otpauth_url,function(err,data){
if(err) throw err;
console.log(data);
})
console.log(secret);
const twofa = require('./utils/2fa');
// To Verify
const verified = twofa.totp.verify({
secret:'>Lu%Z:$W&s2Zf0Qqsm!P%g(R!@i[T5gk',
token:'878287',
})
console.log(verified);
DAmrGharieb commented
first thank you for your attention and spent time ==> the same behaviour ... is there is any contradiction with any windows files permissions i think the problem there i tested the old code and the new one by you give true on various machines and false on one machine even it generate the code successfully but when verifying is the problem
HamzaAitOumghar commented
I tried your solution but the problem still occurs , my code :
import speakeasy from "./speakeasy";
const secret = "AZAD22113ADASDJQF";
const token = speakeasy.totp({
secret: secret,
encoding: "ascii",
step: 600,
});
const isValid = speakeasy.totp.verify({
secret: secret,
token: code,
encoding: "ascii",
window: 2,
});