Open
Description
- 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);
Metadata
Metadata
Assignees
Labels
No labels