Giter Site home page Giter Site logo

speakeasyjs / speakeasy Goto Github PK

View Code? Open in Web Editor NEW
2.7K 59.0 228.0 586 KB

**NOT MAINTAINED** Two-factor authentication for Node.js. One-time passcode generator (HOTP/TOTP) with support for Google Authenticator.

License: MIT License

JavaScript 100.00%
javascript two-factor two-factor-authentication node nodejs node-js mfa hotp totp multi-factor

speakeasy's Introduction

NOT MAINTAINED

Build Status NPM downloads Coverage Status NPM version


Jump toInstall · Demo · Two-Factor Usage · General Usage · Documentation · Contributing · License


Speakeasy is a one-time passcode generator, ideal for use in two-factor authentication, that supports Google Authenticator and other two-factor devices.

It is well-tested and includes robust support for custom token lengths, authentication windows, hash algorithms like SHA256 and SHA512, and other features, and includes helpers like a secret key generator.

Speakeasy implements one-time passcode generators as standardized by the Initiative for Open Authentication (OATH). The HMAC-Based One-Time Password (HOTP) algorithm defined by RFC 4226 and the Time-Based One-time Password (TOTP) algorithm defined in RFC 6238 are supported. This project incorporates code from passcode, originally a fork of Speakeasy, and notp.

Install

npm install --save speakeasy

Demo

This demo uses the generateSecret method of Speakeasy to generate a secret key, displays a Google Authenticator–compatible QR code which you can scan into your phone's two-factor app, and shows the token, which you can verify with your phone. Includes sample code. https://sedemo-mktb.rhcloud.com/

Two-Factor Usage

Let's say you have a user that wants to enable two-factor authentication, and you intend to do two-factor authentication using an app like Google Authenticator, Duo Security, Authy, etc. This is a three-step process:

  1. Generate a secret
  2. Show a QR code for the user to scan in
  3. Authenticate the token for the first time

Generating a key

Use Speakeasy's key generator to get a key.

var secret = speakeasy.generateSecret();
// Returns an object with secret.ascii, secret.hex, and secret.base32.
// Also returns secret.otpauth_url, which we'll use later.

This will generate a secret key of length 32, which will be the secret key for the user.

Now, we want to make sure that this secret works by validating the token that the user gets from it for the first time. In other words, we don't want to set this as the user's secret key just yet – we first want to verify their token for the first time. We need to persist the secret so that we can use it for token validation later.

So, store one of the encodings for the secret, preferably secret.base32, somewhere temporary, since we'll use that in the future to authenticate the user's first token.

// Example for storing the secret key somewhere (varies by implementation):
user.two_factor_temp_secret = secret.base32;

Displaying a QR code

Next, we'll want to display a QR code to the user so they can scan in the secret into their app. Google Authenticator and similar apps take in a QR code that holds a URL with the protocol otpauth://, which you get automatically from secret.otpauth_url.

Use a QR code module to generate a QR code that stores the data in secret.otpauth_url, and then display the QR code to the user. This is one simple way to do it, which generates a PNG data URL which you can put into an <img> tag on a webpage:

// Use the qrcode package
// npm install --save qrcode
var QRCode = require('qrcode');

// Get the data URL of the authenticator URL
QRCode.toDataURL(secret.otpauth_url, function(err, data_url) {
  console.log(data_url);

  // Display this data URL to the user in an <img> tag
  // Example:
  write('<img src="' + data_url + '">');
});

Ask the user to scan this QR code into their authenticator app.

Verifying the token

Finally, we want to make sure that the token on the server side and the token on the client side match. The best practice is to do a token check before fully enabling two-factor authenticaton for the user. This code applies to the first and subsequent token checks.

After the user scans the QR code, ask the user to enter in the token that they see in their app. Then, verify it against the secret.

// Let's say the user says that the token they have is 132890
var userToken = '132890';

// Let's say we stored the user's temporary secret in a user object like above:
// (This is specific to your implementation)
var base32secret = user.two_factor_temp_secret;
// Use verify() to check the token against the secret
var verified = speakeasy.totp.verify({ secret: base32secret,
                                       encoding: 'base32',
                                       token: userToken });

verified will be true if the token is successfully verified, false if not.

If successfully verified, you can now save the secret to the user's account and use the same process above whenever you need to use two-factor to authenticate the user, like during login.

// Example for saving user's token (varies by implementation):
user.two_factor_secret = user.two_factor_temp_secret;
user.two_factor_enabled = true

Now you're done implementing two-factor authentication!

General Usage

var speakeasy = require("speakeasy");

Generating a key

// Generate a secret key.
var secret = speakeasy.generateSecret({length: 20});
// Access using secret.ascii, secret.hex, or secret.base32.

Getting a time-based token for the current time

// Generate a time-based token based on the base-32 key.
// HOTP (counter-based tokens) can also be used if `totp` is replaced by
// `hotp` (i.e. speakeasy.hotp()) and a `counter` is given in the options.
var token = speakeasy.totp({
  secret: secret.base32,
  encoding: 'base32'
});

// Returns token for the secret at the current time
// Compare this to user input

Verifying a token

// Verify a given token
var tokenValidates = speakeasy.totp.verify({
  secret: secret.base32,
  encoding: 'base32',
  token: '123456',
  window: 6
});
// Returns true if the token matches

Verifying a token and calculating a delta

A TOTP is incremented every step time-step seconds. By default, the time-step is 30 seconds. You may change the time-step using the step option, with units in seconds.

// Verify a given token is within 3 time-steps (+/- 2 minutes) from the server
// time-step.
var tokenDelta = speakeasy.totp.verifyDelta({
  secret: secret.base32,
  encoding: 'base32',
  token: '123456',
  window: 2,
  step: 60
});
// Returns {delta: 0} where the delta is the time step difference
// between the given token and the current time

Getting a time-based token for a custom time

var token = speakeasy.totp({
  secret: secret.base32,
  encoding: 'base32',
  time: 1453667708 // specified in seconds
});

// Verify a time-based token for a custom time
var tokenValidates = speakeasy.totp.verify({
  secret: secret.base32,
  encoding: 'base32',
  token: token,
  time: 1453667708
});

Calculating a counter-based token

// Get a counter-based token
var token = speakeasy.hotp({
  secret: secret.base32,
  encoding: 'base32',
  counter: 123
});

// Verify a counter-based token
var tokenValidates = speakeasy.hotp.verify({
  secret: secret.base32,
  encoding: 'base32',
  token: '123456',
  counter: 123
});

Using other encodings

The default encoding (when encoding is not specified) is ascii.

// Specifying an ASCII token for TOTP
// (encoding is 'ascii' by default)
var token = speakeasy.totp({
  secret: secret.ascii
});
// Specifying a hex token for TOTP
var token = speakeasy.totp({
  secret: secret.hex,
  encoding: 'hex'
});

Using other hash algorithms

The default hash algorithm is SHA1.

// Specifying SHA256
var token = speakeasy.totp({
  secret: secret.ascii,
  algorithm: 'sha256'
});
// Specifying SHA512
var token = speakeasy.totp({
  secret: secret.ascii,
  algorithm: 'sha512'
});

Getting an otpauth:// URL and QR code for non-SHA1 hash algorithms

// Generate a secret, if needed
var secret = speakeasy.generateSecret();
// By default, generateSecret() returns an otpauth_url for SHA1

// Use otpauthURL() to get a custom authentication URL for SHA512
var url = speakeasy.otpauthURL({ secret: secret.ascii, label: 'Name of Secret', algorithm: 'sha512' });

// Pass URL into a QR code generator

Specifying a window for verifying HOTP and TOTP

Verify a HOTP token with counter value 42 and a window of 10. HOTP has a one-sided window, so this will check counter values from 42 to 52, inclusive, and return a { delta: n } where n is the difference between the given counter value and the counter position at which the token was found, or undefined if it was not found within the window. See the hotp․verifyDelta(options) documentation for more info.

var token = speakeasy.hotp.verifyDelta({
  secret: secret.ascii,
  counter: 42,
  token: '123456',
  window: 10
});

How this works:

// Set ASCII secret
var secret = 'rNONHRni6BAk7y2TiKrv';

// Get HOTP counter token at counter = 42 
var counter42 = speakeasy.hotp({ secret: secret, counter: 42 });
// => '566646'

// Get HOTP counter token at counter = 45
var counter45 = speakeasy.hotp({ secret: secret, counter: 45 });
// => '323238'

// Verify the secret at counter 42 with the actual value and a window of 10
// This will check all counter values from 42 to 52, inclusive
speakeasy.hotp.verifyDelta({ secret: secret, counter: 42, token: counter42, window: 10 });
// => { delta: 0 } because the given token at counter 42 is 0 steps away from the given counter 42

// Verify the secret at counter 45, but give a counter of 42 and a window of 10
// This will check all counter values from 42 to 52, inclusive
speakeasy.hotp.verifyDelta({ secret: secret, counter: 42, token: counter45, window: 10 });
// => { delta: 3 } because the given token at counter 45 is 0 steps away from given counter 42

// Not in window: specify a window of 1, which only tests counters 42 and 43, not 45
speakeasy.hotp.verifyDelta({ secret: secret, counter: 42, token: counter45, window: 1 });
// => undefined

// Shortcut to use verify() to simply return whether it is verified as within the window
speakeasy.hotp.verify({ secret: secret, counter: 42, token: counter45, window: 10 });
// => true

// Not in window: specify a window of 1, which only tests counters 42 and 43, not 45
speakeasy.hotp.verify({ secret: secret, counter: 42, token: counter45, window: 1 });
// => false

Verify a TOTP token at the current time with a window of 2. Since the default time step is 30 seconds, and TOTP has a two-sided window, this will check tokens between [current time minus two tokens before] and [current time plus two tokens after]. In other words, with a time step of 30 seconds, it will check the token at the current time, plus the tokens at the current time minus 30 seconds, minus 60 seconds, plus 30 seconds, and plus 60 seconds – basically, it will check tokens between a minute ago and a minute from now. It will return a { delta: n } where n is the difference between the current time step and the counter position at which the token was found, or undefined if it was not found within the window. See the totp․verifyDelta(options) documentation for more info.

var verified = speakeasy.totp.verifyDelta({
  secret: secret.ascii,
  token: '123456',
  window: 2
});

The mechanics of TOTP windows are the same as for HOTP, as shown above, just with two-sided windows, meaning that the delta value can be negative if the token is found before the given time or counter.

var secret = 'rNONHRni6BAk7y2TiKrv';

// By way of example, we will force TOTP to return tokens at time 1453853945 and
// at time 1453854005 (60 seconds ahead, or 2 steps ahead)
var token1 = speakeasy.totp({ secret: secret, time: 1453853945 }); // 625175
var token3 = speakeasy.totp({ secret: secret, time: 1453854005 }); // 222636
var token2 = speakeasy.totp({ secret: secret, time: 1453854065 }); // 013052

// We can check the time at token 3, 1453853975, with token 1, but use a window of 2
// With a time step of 30 seconds, this will check all tokens from 60 seconds 
// before the time to 60 seconds after the time
speakeasy.totp.verifyDelta({ secret: secret, token: token1, window: 2, time: 1453854005 });
// => { delta: -2 }

// token is valid because because token is 60 seconds before time
speakeasy.totp.verify({ secret: secret, token: token1, window: 2, time: 1453854005 });
// => true

// token is valid because because token is 0 seconds before time
speakeasy.totp.verify({ secret: secret, token: token3, window: 2, time: 1453854005 });
// => true

// token is valid because because token is 60 seconds after time
speakeasy.totp.verify({ secret: secret, token: token2, window: 2, time: 1453854005 });
// => true

// This signifies that the given token, token1, is -2 steps away from
// the given time, which means that it is the token for the value at
// (-2 * time step) = (-2 * 30 seconds) = 60 seconds ago.

As shown previously, you can also change verifyDelta() to verify() to simply return a boolean if the given token is within the given window.

Documentation

Full API documentation (in JSDoc format) is available below and at http://speakeasyjs.github.io/speakeasy/

Functions

digest(options)Buffer

Digest the one-time passcode options.

hotp(options)String

Generate a counter-based one-time token.

hotp․verifyDelta(options)Object

Verify a counter-based one-time token against the secret and return the delta.

hotp․verify(options)Boolean

Verify a counter-based one-time token against the secret and return true if it verifies.

totp(options)String

Generate a time-based one-time token.

totp․verifyDelta(options)Object

Verify a time-based one-time token against the secret and return the delta.

totp․verify(options)Boolean

Verify a time-based one-time token against the secret and return true if it verifies.

generateSecret(options)Object | GeneratedSecret

Generates a random secret with the set A-Z a-z 0-9 and symbols, of any length (default 32).

generateSecretASCII([length], [symbols])String

Generates a key of a certain length (default 32) from A-Z, a-z, 0-9, and symbols (if requested).

otpauthURL(options)String

Generate an URL for use with the Google Authenticator app.

Typedefs

GeneratedSecret : Object

digest(options) ⇒ Buffer

Digest the one-time passcode options.

Kind: function

Returns: Buffer - The one-time passcode as a buffer.

Param Type Default Description
options Object
options.secret String Shared secret key
options.counter Integer Counter value
[options.encoding] String "ascii" Key encoding (ascii, hex, base32, base64).
[options.algorithm] String "sha1" Hash algorithm (sha1, sha256, sha512).
[options.key] String (DEPRECATED. Use secret instead.) Shared secret key

hotp(options) ⇒ String

Generate a counter-based one-time token. Specify the key and counter, and receive the one-time password for that counter position as a string. You can also specify a token length, as well as the encoding (ASCII, hexadecimal, or base32) and the hashing algorithm to use (SHA1, SHA256, SHA512).

Kind: function

Returns: String - The one-time passcode.

Param Type Default Description
options Object
options.secret String Shared secret key
options.counter Integer Counter value
[options.digest] Buffer Digest, automatically generated by default
[options.digits] Integer 6 The number of digits for the one-time passcode.
[options.encoding] String "ascii" Key encoding (ascii, hex, base32, base64).
[options.algorithm] String "sha1" Hash algorithm (sha1, sha256, sha512).
[options.key] String (DEPRECATED. Use secret instead.) Shared secret key
[options.length] Integer 6 (DEPRECATED. Use digits instead.) The number of digits for the one-time passcode.

hotp․verifyDelta(options) ⇒ Object

Verify a counter-based one-time token against the secret and return the delta. By default, it verifies the token at the given counter value, with no leeway (no look-ahead or look-behind). A token validated at the current counter value will have a delta of 0.

You can specify a window to add more leeway to the verification process. Setting the window param will check for the token at the given counter value as well as window tokens ahead (one-sided window). See param for more info.

verifyDelta() will return the delta between the counter value of the token and the given counter value. For example, if given a counter 5 and a window 10, verifyDelta() will look at tokens from 5 to 15, inclusive. If it finds it at counter position 7, it will return { delta: 2 }.

Kind: function

Returns: Object - On success, returns an object with the counter difference between the client and the server as the delta property (i.e. { delta: 0 }).

Param Type Default Description
options Object
options.secret String Shared secret key
options.token String Passcode to validate
options.counter Integer Counter value. This should be stored by the application and must be incremented for each request.
[options.digits] Integer 6 The number of digits for the one-time passcode.
[options.window] Integer 0 The allowable margin for the counter. The function will check "W" codes in the future against the provided passcode, e.g. if W = 10, and C = 5, this function will check the passcode against all One Time Passcodes between 5 and 15, inclusive.
[options.encoding] String "ascii" Key encoding (ascii, hex, base32, base64).
[options.algorithm] String "sha1" Hash algorithm (sha1, sha256, sha512).

hotp․verify(options) ⇒ Boolean

Verify a counter-based one-time token against the secret and return true if it verifies. Helper function for `hotp.verifyDelta()`` that returns a boolean instead of an object. For more on how to use a window with this, see hotp.verifyDelta.

Kind: function

Returns: Boolean - Returns true if the token matches within the given window, false otherwise.

Param Type Default Description
options Object
options.secret String Shared secret key
options.token String Passcode to validate
options.counter Integer Counter value. This should be stored by the application and must be incremented for each request.
[options.digits] Integer 6 The number of digits for the one-time passcode.
[options.window] Integer 0 The allowable margin for the counter. The function will check "W" codes in the future against the provided passcode, e.g. if W = 10, and C = 5, this function will check the passcode against all One Time Passcodes between 5 and 15, inclusive.
[options.encoding] String "ascii" Key encoding (ascii, hex, base32, base64).
[options.algorithm] String "sha1" Hash algorithm (sha1, sha256, sha512).

totp(options) ⇒ String

Generate a time-based one-time token. Specify the key, and receive the one-time password for that time as a string. By default, it uses the current time and a time step of 30 seconds, so there is a new token every 30 seconds. You may override the time step and epoch for custom timing. You can also specify a token length, as well as the encoding (ASCII, hexadecimal, or base32) and the hashing algorithm to use (SHA1, SHA256, SHA512).

Under the hood, TOTP calculates the counter value by finding how many time steps have passed since the epoch, and calls HOTP with that counter value.

Kind: function

Returns: String - The one-time passcode.

Param Type Default Description
options Object
options.secret String Shared secret key
[options.time] Integer Time in seconds with which to calculate counter value. Defaults to Date.now().
[options.step] Integer 30 Time step in seconds
[options.epoch] Integer 0 Initial time since the UNIX epoch from which to calculate the counter value. Defaults to 0 (no offset).
[options.counter] Integer Counter value, calculated by default.
[options.digits] Integer 6 The number of digits for the one-time passcode.
[options.encoding] String "ascii" Key encoding (ascii, hex, base32, base64).
[options.algorithm] String "sha1" Hash algorithm (sha1, sha256, sha512).
[options.key] String (DEPRECATED. Use secret instead.) Shared secret key
[options.initial_time] Integer 0 (DEPRECATED. Use epoch instead.) Initial time since the UNIX epoch from which to calculate the counter value. Defaults to 0 (no offset).
[options.length] Integer 6 (DEPRECATED. Use digits instead.) The number of digits for the one-time passcode.

totp․verifyDelta(options) ⇒ Object

Verify a time-based one-time token against the secret and return the delta. By default, it verifies the token at the current time window, with no leeway (no look-ahead or look-behind). A token validated at the current time window will have a delta of 0.

You can specify a window to add more leeway to the verification process. Setting the window param will check for the token at the given counter value as well as window tokens ahead and window tokens behind (two-sided window). See param for more info.

verifyDelta() will return the delta between the counter value of the token and the given counter value. For example, if given a time at counter 1000 and a window of 5, verifyDelta() will look at tokens from 995 to 1005, inclusive. In other words, if the time-step is 30 seconds, it will look at tokens from 2.5 minutes ago to 2.5 minutes in the future, inclusive. If it finds it at counter position 1002, it will return { delta: 2 }. If it finds it at counter position 997, it will return { delta: -3 }.

Kind: function

Returns: Object - On success, returns an object with the time step difference between the client and the server as the delta property (e.g. { delta: 0 }).

Param Type Default Description
options Object
options.secret String Shared secret key
options.token String Passcode to validate
[options.time] Integer Time in seconds with which to calculate counter value. Defaults to Date.now().
[options.step] Integer 30 Time step in seconds
[options.epoch] Integer 0 Initial time since the UNIX epoch from which to calculate the counter value. Defaults to 0 (no offset).
[options.counter] Integer Counter value, calculated by default.
[options.digits] Integer 6 The number of digits for the one-time passcode.
[options.window] Integer 0 The allowable margin for the counter. The function will check "W" codes in the future and the past against the provided passcode, e.g. if W = 5, and C = 1000, this function will check the passcode against all One Time Passcodes between 995 and 1005, inclusive.
[options.encoding] String "ascii" Key encoding (ascii, hex, base32, base64).
[options.algorithm] String "sha1" Hash algorithm (sha1, sha256, sha512).

totp․verify(options) ⇒ Boolean

Verify a time-based one-time token against the secret and return true if it verifies. Helper function for verifyDelta() that returns a boolean instead of an object. For more on how to use a window with this, see totp.verifyDelta.

Kind: function

Returns: Boolean - Returns true if the token matches within the given window, false otherwise.

Param Type Default Description
options Object
options.secret String Shared secret key
options.token String Passcode to validate
[options.time] Integer Time in seconds with which to calculate counter value. Defaults to Date.now().
[options.step] Integer 30 Time step in seconds
[options.epoch] Integer 0 Initial time since the UNIX epoch from which to calculate the counter value. Defaults to 0 (no offset).
[options.counter] Integer Counter value, calculated by default.
[options.digits] Integer 6 The number of digits for the one-time passcode.
[options.window] Integer 0 The allowable margin for the counter. The function will check "W" codes in the future and the past against the provided passcode, e.g. if W = 5, and C = 1000, this function will check the passcode against all One Time Passcodes between 995 and 1005, inclusive.
[options.encoding] String "ascii" Key encoding (ascii, hex, base32, base64).
[options.algorithm] String "sha1" Hash algorithm (sha1, sha256, sha512).

generateSecret(options) ⇒ Object | GeneratedSecret

Generates a random secret with the set A-Z a-z 0-9 and symbols, of any length (default 32). Returns the secret key in ASCII, hexadecimal, and base32 format, along with the URL used for the QR code for Google Authenticator (an otpauth URL). Use a QR code library to generate a QR code based on the Google Authenticator URL to obtain a QR code you can scan into the app.

Kind: function

Returns: A GeneratedSecret object

Param Type Default Description
options Object
[options.length] Integer 32 Length of the secret
[options.symbols] Boolean false Whether to include symbols
[options.otpauth_url] Boolean true Whether to output a Google Authenticator-compatible otpauth:// URL (only returns otpauth:// URL, no QR code)
[options.name] String The name to use with Google Authenticator.
[options.qr_codes] Boolean false (DEPRECATED. Do not use to prevent leaking of secret to a third party. Use your own QR code implementation.) Output QR code URLs for the token.
[options.google_auth_qr] Boolean false (DEPRECATED. Do not use to prevent leaking of secret to a third party. Use your own QR code implementation.) Output a Google Authenticator otpauth:// QR code URL.
[options.issuer] String The provider or service with which the secret key is associated.

generateSecretASCII([length], [symbols]) ⇒ String

Generates a key of a certain length (default 32) from A-Z, a-z, 0-9, and symbols (if requested).

Kind: function

Returns: String - The generated key.

Param Type Default Description
[length] Integer 32 The length of the key.
[symbols] Boolean false Whether to include symbols in the key.

otpauthURL(options) ⇒ String

Generate a Google Authenticator-compatible otpauth:// URL for passing the secret to a mobile device to install the secret.

Authenticator considers TOTP codes valid for 30 seconds. Additionally, the app presents 6 digits codes to the user. According to the documentation, the period and number of digits are currently ignored by the app.

To generate a suitable QR Code, pass the generated URL to a QR Code generator, such as the qr-image module.

Kind: function

Throws: Error if secret or label is missing, or if hotp is used and a counter is missing, if the type is not one of hotp or totp, if the number of digits is non-numeric, or an invalid period is used. Warns if the number of digits is not either 6 or 8 (though 6 is the only one supported by Google Authenticator), and if the hashihng algorithm is not one of the supported SHA1, SHA256, or SHA512.

Returns: String - A URL suitable for use with the Google Authenticator.

See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format

Param Type Default Description
options Object
options.secret String Shared secret key
options.label String Used to identify the account with which the secret key is associated, e.g. the user's email address.
[options.type] String "totp" Either "hotp" or "totp".
[options.counter] Integer The initial counter value, required for HOTP.
[options.issuer] String The provider or service with which the secret key is associated.
[options.algorithm] String "sha1" Hash algorithm (sha1, sha256, sha512).
[options.digits] Integer 6 The number of digits for the one-time passcode. Currently ignored by Google Authenticator.
[options.period] Integer 30 The length of time for which a TOTP code will be valid, in seconds. Currently ignored by Google Authenticator.
[options.encoding] String ascii Key encoding (ascii, hex, base32, base64). If the key is not encoded in Base-32, it will be reencoded.

GeneratedSecret : Object

Kind: global typedef

Properties

Name Type Description
ascii String ASCII representation of the secret
hex String Hex representation of the secret
base32 String Base32 representation of the secret
qr_code_ascii String URL for the QR code for the ASCII secret.
qr_code_hex String URL for the QR code for the hex secret.
qr_code_base32 String URL for the QR code for the base32 secret.
google_auth_qr String URL for the Google Authenticator otpauth URL's QR code.
otpauth_url String Google Authenticator-compatible otpauth URL.

Contributing

We're very happy to have your contributions in Speakeasy.

Contributing code — First, make sure you've added tests if adding new functionality. Then, run npm test to run all the tests to make sure they pass. Next, make a pull request to this repo. Thanks!

Filing an issue — Submit issues to the GitHub Issues page.

Maintainers

License

This project incorporates code from passcode, which was originally a fork of speakeasy, and notp, both of which are licensed under MIT. Please see the LICENSE file for the full combined license.

Icons created by Gregor Črešnar, iconoci, and Danny Sturgess from the Noun Project.

speakeasy's People

Contributors

cgarvey avatar cmaster11 avatar connor4312 avatar freewil avatar haavardw avatar jakelee8 avatar markbao avatar mashihua avatar mikepb avatar mslosarek avatar petejodo avatar railsstudent avatar sakkaku avatar zzozz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

speakeasy's Issues

Local Application

Is there anyway that you can see using your library locally for a local node-webkit app? Assuming the application is working offline.

Struggling with protecting the secret.

Voodoo: Running this in a docker container isn't working?

So, total voodoo.

If I run my nodeapp just with nodemon.. I can generate QR codes and verify them etc.

If I startup the exact same app, in a docker container.. it generates a QR code etc just fine, but then they fail to verify.

Anyone have any clues?

replace ezcrypto with buffer?

Is there a reason for not using node's built-in buffer?

var digest_bytes = ezcrypto.util.hexToBytes(digest);
// instead of
var digest_bytes = new Buffer(digest, 'hex');

Also, I see a few places where you appear to be converting from hex to a binary string instead of using buffer, but then you immediately convert it to a buffer.

Would you be open to some cleanup of this code? Or where you doing it that way for a particular reason?

every time failstotp.verify()

[HELP WANTED]
Hi, I want to generate a token that will last for a certain time, say for 7200 second, that is two hour.
what is want to do is, it will give me false when I'm ging to verify a token two hour after generation. that is will be valid till two hour of generation.

Do I need to use verifyDelta? its every time giving me false when I'm setting time.

and my code is below to generate the token:


 var secret = speakeasy.generateSecret();
        var token = speakeasy.totp({
            secret: secret.base32,
            encoding: 'base32',
           time : 7200
              });



and here is the verify:

var verified = speakeasy.totp.verify({
          secret: secret_str,
          encoding: 'base32',
          token: req.body.unique_code,
         window: 240   //as window value 1 for each 30 secoends..
      });

Doesn't agree with Google Authenticator

I'm unable to get this to agree with Google Authenticator. onceler does, however. The otp var generated by the two samples below should be equal. The one from onceler agrees with the Google Authenticator app on my iPhone, but speakeasy does not.

onceler

var TOKEN = get_token_used_by_google_authenticator();
var TOTP = require("onceler").TOTP;

var otp = (new TOTP(TOKEN)).now();

speakeasy

var TOKEN = get_token_used_by_google_authenticator();
var speakeasy = require("speakeasy")

var otp = speakeasy.totp({key: TOKEN});

(some) hex encoded keys not parsed correctly

I could not generate correct totp codes. Was using a hex encoded key. I think there is a problem handling some special ASCII chars in my hex key. Anyway here is the fix that worked for me:

Change line:
key = speakeasy.hex_to_ascii(key);
into:
key = new Buffer(speakeasy.hex_to_ascii(key), 'ascii');

Perhaps it's even better to change this line instead:
var hmac = crypto.createHmac('sha1', new Buffer(key));
into
var hmac = crypto.createHmac('sha1', new Buffer(key, 'ascii'));

That way it will also handle all 'ascii' encoded keys correctly I guess... Although you better test what this gives when passing a base32 coded key then;-)

Functional example

Can you provide a functional example that we can pull? Just so it serves a simple static html? Thanks

Security: brute force possible when badly implemented (please improve documentation to help implementers)

I love the library and it has been very helpful for me.

Recently, my implementation of speakeasy.totp failed a penetration test. I wrote a writeup on my findings (with a code sample to show how common this can happen with a bad implementation+configuration).

The issue: please improve the documentation (especially around passing in "secret" and "window" parameters). If the window parameter is too large, "false positives" (successful attacks against the TOTP) become more frequent. If the "secret" passed to the speakeasy.totp.verify function is an empty string (not undefined or null), the verify function becomes much easier to attack via brute force.

I don't think any code changes are necessarily needed (an empty string "secret" might possibly be a valid input for some use cases), but updates to the docs would be very welcome, especially to those of us who only know enough about encryption "to be dangerous".

Error on npm install?

When I run this command on my Debian box:
npm install --save speakeasy

I get the following errors below? Never have an issue with an npm install, is it just me?

npm ERR! addLocal Could not install /usr/local/nginx/node_services/node_modules/speakeasy
npm ERR! Linux 4.7.0-x86_64-linode72
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install" "speakeasy"
npm ERR! node v7.0.0
npm ERR! npm v3.10.9
npm ERR! path /usr/local/nginx/node_services/node_modules/speakeasy
npm ERR! code ENOENT
npm ERR! errno -2
npm ERR! syscall open

npm ERR! enoent ENOENT: no such file or directory, open '/usr/local/nginx/node_services/node_modules/speakeasy'
npm ERR! enoent ENOENT: no such file or directory, open '/usr/local/nginx/node_services/node_modules/speakeasy'
npm ERR! enoent This is most likely not a problem with npm itself
npm ERR! enoent and is related to npm not being able to find a file.
npm ERR! enoent

npm ERR! Please include the following file with any support request:
npm ERR! /usr/local/nginx/node_services/npm-debug.log
root@indoworld:/usr/local/nginx/node_services# npm install --save speakeasy
npm ERR! addLocal Could not install /usr/local/nginx/node_services/node_modules/speakeasy
npm ERR! Linux 4.7.0-x86_64-linode72
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install" "--save" "speakeasy"
npm ERR! node v7.0.0
npm ERR! npm v3.10.9
npm ERR! path /usr/local/nginx/node_services/node_modules/speakeasy
npm ERR! code ENOENT
npm ERR! errno -2
npm ERR! syscall open

Unsatisfying HOTP Length Guarantee

After running some extensive tests, I noticed the length option is not guaranteed to be accurate for HOTP (the length option you pass in to specify the corresponding length of the password). For example:

var results = [];
for(var a = 0; a < 100; a++)
{
  var key = speakeasy.generate_key({}), 
    counter = Math.round(Math.random() * 1000),
    enc = speakeasy.hotp({key: key, counter: counter, length: 10});
  results.push(enc);
}
// results:
/*
["1538693343","1415344825","6","5","1651991530","4","1527078255","1629965348","3","1193455652",
"1952958576","1258579313","1075948159","1","2","1267393198","8","0","1752728376","7",
"1428927373","1627528678","4","1525998232","9","6","8","3","1217686650","33",
"1716897211","1418827434","1652848058","1046234599","1581887919","6","1334447569","1125724882","1411376761","1038550269",
"1768316833","1222746954","4","7","1","4","2140171107","1393057192","1553580072","1778832949",
"1191522822","8","1263568045","1426382930","2","3","2","1175991028","1312065751","1871039662",
"489","6","1","1342529250","5","1286581247","1443847244","2146864109","4","7",
"2038352328","1303497318","2099247576","1450618013","1","1431085013","1274013039","1476435986","2051093635","1572660008",
"6","9","1289089011","7","1309585672","1644834058","1735330145","1326514638","1931307844","3",
"1303497318","4","2132910273","33","6","1354650394","2116173448","1430563546","4","8"]
*/

Note how fairly regularly, the generated keys are not of the length promised in the contract by length property.

use notp instead of own implementation

The notp module lacks some of the features of this module, but the code surrounding the core hotp / totp is cleaner and more idiomatic to node.js.

Would you accept an api-compatible rewrite based on notp? If so I would be willing to do that.

symbols option doesn't work

Running the following command includes symbols in the generated key:

speakeasy.generate_key({ length: 12, symbols: false });

LICENSE

Under which license conditions this code is published/released?

Always getting speakeasy.totp.verify(options) as false

Hi All,

I'm trying to validate otp using speakeasy. I'm able to generate otp and validate otp within the same request. But whenever I'm trying to validate otp in two phases like: Generate OTP in one step, and then Validate OTP in second step. I'm always getting verify as false.

Below is the code that I'm using for validating the OTP. Please let me know what is the wrong in it.

var express = require('express');
var router = express.Router();
var speakeasy = require('speakeasy');

router.route('/generateotp').post(function(req, res){
var userid= req.body.userid;
var secret = speakeasy.generateSecret({length: 20});
var token = speakeasy.totp({
secret: secret.base32,
encoding: 'base32'
});
});

router.route('/validateotp').post(function(req, res){
var userToken= req.body.code;
var verified = speakeasy.totp.verify({
secret: MY_PREVIOUSLY_GENERATED_SECRET
encoding: 'base32',
token: userToken
});
console.log(verified);
});

module.exports = router;

In the above code, Why I'm always getting verified as false on /validateotp request?

Error using base32 encoding (browser support)

Hi everybody,

I am using speakeasy in an Ember application with ember-browserify addon: https://www.npmjs.com/package/ember-browserify

Just encountered that the base32 encoding seems to not work at all.
Considering the following code:

var secret = speakeasy.generateSecret(); // default length is 32
var secretKey = secret.base32;
var token = speakeasy.totp({
  secret: secretKey,
  encoding: 'base32'
});

So, just a secret generation and subsequent using it to create a new token leads to the following error:

"list" argument must be an Array of Buffers

The same issue with the speakeasy.totp.verify() method.

I've found the reason in the following code from index.js (lines 39-42):

// convert secret to buffer
if (!Buffer.isBuffer(secret)) {
  secret = encoding === 'base32' ? base32.decode(secret)
    : new Buffer(secret, encoding);
}

The base32.decode(secret) returns an array and not a Buffer.
I've changed the code and got it working:

// convert secret to buffer
if (!Buffer.isBuffer(secret)) {
  secret = encoding === 'base32' ? new Buffer(base32.decode(secret)) : new Buffer(secret, encoding);
}

Verification keeps failing for HEX and Base32 only ASCII working fine

I've been trying to understand the module for house now, but couldn't get to understand what is going on. I followed the steps in the documentation, and it still isn't verifying. This is my code

function getPublicKey() {

	var secret = speakeasy.generateSecret({length: 25});
	var base32Secret = secret["base32"];
	console.log("Secret: ", base32Secret);

	var token = speakeasy.totp({base32Secret, encoding: 'base32', window: 6});

	console.log("MainToken: ", token);

	var tokenValidates = speakeasy.totp.verify({
	  secret: base32Secret,
	  encoding: 'base32',
	  token: token,
	  window: 6
	});

	console.log("TokenValidate: ", tokenValidates);

	var newTokenValidates = speakeasy.totp.verify({
	  secret: base32Secret,
	  encoding: 'base32',
	  token: '1234356'
	});

	console.log("NewTokenValidate: ", newTokenValidates);
}

And, this is my response:

Secret:  IJQSCWS3KJHE2KLCJA4UOUJYONNHQWTPHIXTSIJM
MainToken:  615020
TokenValidate:  false
NewTokenValidate:  false

And then, I tried for ASCII and it works fine

function getPublicKey() {

	var secret = speakeasy.generateSecret({length: 25});
	var acsiiSecret = secret["ascii"];
	console.log("Secret: ", acsiiSecret);

	var token = speakeasy.totp({secret: acsiiSecret});

	console.log("MainToken: ", token);

	var tokenValidates = speakeasy.totp.verify({
	  secret: acsiiSecret,
	  encoding: 'ascii',
	  token: token
	});

	console.log("TokenValidate: ", tokenValidates);

	var newTokenValidates = speakeasy.totp.verify({
	  secret: acsiiSecret,
	  encoding: 'ascii',
	  token: '1234356'
	});

	console.log("NewTokenValidate: ", newTokenValidates);
}

And the result:

Secret:  fm:Scz4n7TDlZXA/}0OPxR3[<
MainToken:  228114
TokenValidate:  true
NewTokenValidate:  false

Default values of otpAuthURL are inconsistent with the documentation in README.md

In README.md, the default values of algorithm, digits, period are sha1, 6 and 30 respectively. In code, no value is assigned and it works in google authenticator app for now because these parameters are ignored.

In code, the default value of encoding is ascii but README.md leaves the default value blank,
I would like to make the doc and code consistent, so the code does what the documentation says.

TypeError: item.copy is not a function (browser support)

I have used browserify to create a standalone js file, I am able to generate a QR code but

  1. When I am trying to totp.verify using base32 encoding I am getting an error "Uncaught TypeError: item.copy is not a function" the exact statement is "item.copy(buf,pos) in the Buffer.concat function. Seems like a dependency is missing ?
  2. There are no errors shown while using hex and ascii encoding but the verify method still doesn't work.

Below are the code snippets

Generate secret, display QR code, also generate a token

requirejs(['speakeasy', 'qrcode'], function(speakeasy, QRCode) {
            _secret = speakeasy.generateSecret({ length: 20 });
            _speakeasy = speakeasy;
            $('#qrcode').qrcode({
                width: 128,
                height: 128,
                text: _secret.otpauth_url
            });


            // printing a generated token to validate the token with Google Authorizer
            var token = speakeasy.totp({
                secret: _secret.ascii,
                //encoding: 'base32'
            });
            $('#qrcode').append(token);
            $('#container').delegate('#loginButton', 'click', handleLoginClick);
        }); 

Verify user token

var userToken = $('#usertoken').val();
        console.log('userToke:',userToken,'secret',_secret.base32);
        var verified = _speakeasy.time.verify({
            secret: _secret.ascii,
            token: userToken,
            window: 2,
            step: 60
        });
        console.log('verified:',verified);

Any suggestions on how to fix this ? Thanks

Newb question / can't seem to find it documented

Are there examples of integrating speakeasyjs with passportjs? It seems like that is the route a person would go, and i'm amazed I can't find a quick example of that integration :)

(or am I totally missing it, and there is no reason to use both together?)

first otp generated inconsistent with step parameter

I have been testing totp function , the first time is running the program and calls the function speakeasy.totp(...)the changing time of the one time password is inconsistent with the step parameter.
But after the first time works perfectly. Follows in attach one example:
with parameter step = 10 and I am calling the function approximately 2 in 2 secs.
I set the time: parseInt(Date.now()/1000)

screen shot 2014-08-09 at 03 26 18

Sending secret key over HTTPs

Secret key is supposed to be unpredictable and kept secret because anyone who knows the secret can generate the code at any time.

Speakeasy sends it over an HTTPs request.

While we're sending it to Google - can we trust it? Some might say 'yes', some might say 'no'; infosec says 'no' - we're sending it over an HTTPs GET request: even if it is a request over SSL, it doesn't secure the data passed via GET, and so the secret is exposed while it gets transmitted to any party who has control over the network.

Speakeasy should thus try to generate a QR-Code on the server side.

speakeasy.time(); not showing matching Google Auth/Console output OTP

I attempted to use the example in the github readme to verify that the OTP generated from my phone/device matched the speakeasy.time();... Google shows one OTP, and the output to my console shows a different OTP.

Below is the code I used:

Example OTP QR: https://www.google.com/chart?chs=166x166&chld=L|0&cht=qr&chl=otpauth://totp/SecretKey%3Fsecret=KY7TSZRWFZBXCMJGHRED6PDOPBSS4WCK

    1. added QR code generated from google API to my google authenticator app
    1. speakeasy.time({key: 'KY7TSZRWFZBXCMJGHRED6PDOPBSS4WCK', encoding: 'base32'}); // see the base32 result above

Result:

  • Google Authenticator: 741825
  • Console.log(): 241101

Any suggestions?

API idea: add `expiry` option for longer-term tokens

Currently, the window option option as describing maximum acceptable the margin of error:

  • (HOTP) the maximum acceptable difference between the authoritative counter and user-provided token. e.g. 0 <= delta <= +window
  • (TOTP) the maximum difference in seconds between the authoritative clock and the user-provided token. e.g. -window <= delta <= +window

For TOTP, it may be useful to allow the user-generated token to be valid for a longer time in the future. For example, a site administrator may want to allow password reset tokens to remain valid for up to 24 hours. In another example, an e-commerce site may wish to send out coupon codes that have a limited window of use in the future. The current implementation of the window option does not easily allow for either of these scenarios as the programmer would need to adjust the time and window values manually.

I propose adding an expiry option valid solely for TOTP that sets the "lookahead window" such that a token is valid iff:

  • -window <= delta <= expiry + window

where delta represents the counter difference between the authoritative clock and the user-provided token.

Implementation of the aforementioned features would be possible by generating TOTP tokens using the following options:

Scenario expiry (seconds) time (seconds since UNIX epoch)
24-hour password reset tokens 24*60*60 (default)
coupon code valid for 8 hours starting in 7 days 8*60*60 Date.now() + 7*24*60*60
Festival ticket valid from December 1, 2016 at 10 AM PST until December 3, 2016 at 11:59 PM (Date.parse('Dec 03 2016 23:59:00 PST') - Date.parse('Dec 01 2016 10:00:00 PST')) / 1000 Date.parse('Dec 01 2016 10:00:00 PST') / 1000

As with the secret option, the user will need to provide the expiry and time options on verification as well to achieve the desired effect. Thus, the library agrees to only validate the token within the provided time window and marks all tokens as invalid outside the time window.

The server still only computes ± window + 1 tokens at each comparison because we know that the starting time for which the token was generated is fixed. As both the expiry and time options are provided at validation, the token is valid iff both these conditions hold:

  • time - window <= now() <= time + expiry + window
  • -window <= delta@time <= +window

where delta@time represents the calculated delta for the given time option.

As an alternative to storing the time and expiry values in a database, developers may wish to encode these values into a signed string (e.g. using HMAC). On decoding, the developer verifies that the string has not been tampered with and passes the appropriate options to speakeasy for validation.

Additional thoughts:

  • tolerance could be a better name for the window option.
  • until could be used to accept the absolute time version of expiry.

Please let me know what you think!

Cheers.

Publish updated SpeakEasy

Glad to see the changes from #21 and #33 were merged in! However, the npm version has not yet been bumped from 1.0.3 that was published three years ago. It'd be great to see a 1.0.4 😄

Dependency without a license

Hi, I have noticed that one of your dependancies [email protected] https://github.com/ElmerZhang/ezcrypto does not have a license attached to it.

I have created and issue on their repo asking to add a license file: ElmerZhang/ezcrypto#4

Your code has an MIT license which is great, but unfortunately if you depend on code with no license it can not really be MIT code (say ezcrypto claims their code is GPLv3 tomorrow). That code is technically proprietary (by default). See: https://stackoverflow.com/questions/1190628/what-license-is-public-code-under-if-no-license-is-specified

Please help me in reaching out to ElmerZhang or consider picking a permissive licensed alternative.

No QR options in Google Authenticator

Hi, I've seen that Google Authenticator also gives the option to simple input a key instead of scaning a QR, I wonder what's the format of that key.

It asks for a name, key and Time-based/Counter-Based

This seems very useful when the user needs to enrol in a mobile device.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.