npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

secure-chest

v4.0.1

Published

Sign and Encrypt Data

Downloads

431

Readme

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.

import { Chester, errors } from 'secure-chest';

const { DecryptionExpiredError } = 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 (e instanceof DecryptionExpiredError) {
    // ... re-authenticate with facebook ...
  }
}

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

import { Chester } from 'secure-chest';

const { DecryptionExpiredError } = 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 (e 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

import { Chester } from 'secure-chest';

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

import crypto from 'crypto';
import { Crypter } from 'secure-chest';

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.