Nero Punished Miscreants
Share your code. npm Orgs help your team discover, share, and reuse code. Create a free org »

secure-chest

1.5.2 • Public • Published

Secure Chest

Build Status Test Coverage Dependabot Status Dependencies NPM Downloads Semantic-Release Gardener

Web-safe Encryption and Signing of Data

Use Case

Intended for storing data with untrusted party. Useful when storing data on server is expensive, forbidden, inconvenient or impossible.

Data is first signed and then, together with a timestamp, encrypted into a "chest" using a secret. Data can be extracted again and checked for consistency and freshness using the same secret.

Encoded Data is Url Safe and satisfies the regular expression ^[A-Za-z0-9\-_]+$. Internally Gzip is used when this shortens the tokens.

Getting Started

$ npm i --save secure-chest

Below is an example flow that allows users to be signed in without persisting any information on the server.

const { Chester } = require("secure-chest");
const { DecryptionExpiredError } = require("secure-chest").errors;
 
const chester = Chester("SECRET-ENCRYPTION-KEY", {
  name: "facebook-auth",
  maxAgeInSec: 60 * 60 // require re-auth every hour
});
 
// ... facebook oauth flow ...
 
const token = getFacebookSessionToken();
const chest = chester.lockObj({ token });
 
// ... store chest with client ...
 
// ... time passes ...
 
// .. client makes request and provides chest ...
 
try {
  if (isValidFacebookUser(chester.unlockObj(chest))) {
    // welcome back
  }
} catch (e) {
  if (instanceof DecryptionExpiredError) {
    // ... re-authenticate with facebook ...
  }
}

Or to create an unsubscribe link without storing information on the server one could use it as follows.

const { Chester } = require("secure-chest");
const { DecryptionExpiredError } = require("secure-chest").errors;
 
const chester = Chester("SECRET-ENCRYPTION-KEY", {
  name: "email-unsubscribe",
  maxAgeInSec: 60 * 60 * 24 * 90 // link is valid for 90 days
});
 
// could also include hashed password-salt if unsubscribe links should be invalidated when password changes
const unsubscribeLink = `https://domain.com/unsubscribe?token=${chester.lockObj({ userId, userEmail })}`;
 
// ... generate and send email to user ...
 
// ... user clicks unsubscribe link ...
 
const token = getQueryParam("token");
 
try {
  const info = chester.unlockObj(token);
  const user = findUser(info.userId, info.userEmail);
  if (user) {
    unsubscribe(user);
  } else {
    // user does not exist or email address was changed
  }
} catch (e) {
  if (instanceof DecryptionExpiredError) {
    // unsubscribe link has expired
  }
}

Chester

Exposes main functionality.

Uses GZip internally when this shortens the output.

Parameters

secret

Type: string or Buffer

Secret used to encrypt data. If string is provided it is converted into Buffer internally using provided encoding.

name

Type: string
Default: default

Name of this Chester. A Chester can not open chests if Chester with different name but same secret locked them. Ease-of-life, so one can use same secret for different Chester.

Internally name is merged with provided secret and passed into underlying Crypter.

encoding

Type: constants.ENCODING
Default: constants.ENCODING.utf8

Encoding used to convert between strings and Buffers. For most cases utf8 is suitable.

zeroTime

Type: number
Default: 1514764800

Used to delay year 2038 problem. Should never be changed.

Necessary since timestamp is stored as 4 bytes.

maxAgeInSec

Type: number
Default: 60

Maximum age in seconds before chest expires and DecryptionExpiredError is thrown when trying to unlock it.

When value is changed it is automatically changed for all previously created chests, since chests only store a timestamp.

gzip

Type: constant.GZIP_MODE
Default: constant.GZIP_MODE.AUTO

Overwrite gzip mode. By default gzip mode is only used when output is shortened.

gzipLevel

Type: zlib.constants.* Default: zlib.constants.Z_BEST_COMPRESSION

Desired zlib compression.

encryption

See Cryper below

ivLength

See Cryper below

Errors

Exported from top level errors.

EncryptionError

General Encryption Error that all Encryption Errors inherit from.

EncryptionJsonError

Thrown from lockObj when JSON.stringify fails.

DecryptionError

General Decryption Error that all Decryption Errors inherit from.

DecryptionIntegrityError

Provided data can not be decrypted. Can be indication for invalid data or incorrect secret or name.

DecryptionSignatureError

Data was decrypted successfully, but signature did not match. Can also indicate invalid data or an incorrect secret or name.

Also thrown when context was changed.

DecryptionTimeTravelError

The chest is not valid yet. This usually only happens when the zeroTime is changed.

DecryptionExpiredError

The chest has expired. Not risen when expire option is set to false.

DecryptionGunzipError

The gzip content of the chest is invalid. This should never happen.

DecryptionJsonError

Thrown from unlockObj when JSON.parse fails.

Functions

lock

lock(treasure, options = {})

Create and "lock" new chest. Takes data to encrypt as first argument and optional options as second argument.

lockObj

Wraps lock, and JSON.stringify is applied to first argument. On failure EncryptionJsonError is thrown.

unlock

unlock(chest, options = {})

Unlock a chest and returns data. Takes data to decrypt as first argument and options as optional second argument.

This method can throw various errors (see section).

unlockObj

Wraps unlock, and JSON.parse is applied to return value. On failure DecryptionJsonError is thrown.

Options

contexts

Type: String[]
Default: []

When unlocking chest where contexts have been provided to lock it, unlocking requires the contexts to be identical. Useful for IP address or User-Agent if changes should invalidate the chest.

expire

Type: Boolean
Default: true

When set to false the DecryptionExpiredError is never risen.

Example

const Chester = require("secure-chest").Chester;
 
const chester = Chester("SECRET-ENCRYPTION-KEY");
const data = "Some Text";
const chest = chester.lock(data);
chester.unlock(chest);
// => "Some Text"

Crypter

Used to encrypt and decrypt data using aes-256-cbc with 16 bit random IV by default.

Deals only with Buffers and produced web-safe base64 and hence is encoding independent.

Important: Errors are not explicitly handled.

Functions

encrypt

encrypt(Buffer)

Takes a Buffer and encrypts it as a web-safe base64 encoded string.

decrypt

decrypt(Base64)

Takes a web-safe base64 encoded string and decrypts it into a Buffer.

Parameters

secret

Type: Buffer

Secret used to encrypt data. Internally this gets hashed.

encryption

Type: string
Default: aes-256-cbc

Defines encryption algorithm. IV length must be compatible.

ivLength

Type: number
Default: 16

Defines length of IV. Must be compatible with encryption.

Example

const crypto = require('crypto');
const Crypter = require("secure-chest").Crypter;
 
const crypter = Crypter(crypto.randomBytes(64));
 
const data = crypto.randomBytes(64);
const encrypted = crypter.encrypt(data); // non-deterministic due to IV
const decrypted = crypter.decrypt(encrypted);
 
Buffer.compare(data, decrypted);
// => 0

Constants

GZIP_MODE

Values AUTO, NEVER, FORCE

Defines gzip mode used internally.

ENCODING

Values utf8, ascii, latin1, binary

Defines encoding for string to buffer conversions.

Utility Functions

The functions toUrlSafeBase64 and fromUrlSafeBase64 are exposed.

toUrlSafeBase64(Buffer)

fromUrlSafeBase64(Base64)

Implementation Notes

This project is considered complete and won't see any major features or changes.

Input values are heavily checked and TypeError is raised if invalid.

Signature Observations

By default GZip is only used when this shortens the output. One bit in the signature indicates if gzip is used and hence only len - 1 bits are the "true" signature.

Acceptable since signature is 16 bytes and this doesn't increase collisions significantly.

install

npm i secure-chest

Downloadsweekly downloads

31

version

1.5.2

license

MIT

homepage

github.com

repository

Gitgithub

last publish

collaborators

  • avatar
Report a vulnerability