Web-safe Encryption and Signing of Data
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.
$ 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, DecryptionExpiredError } = require("secure-chest");
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.
const { Chester, DecryptionExpiredError } = require("secure-chest");
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
}
}
Exposes main functionality.
Type: string
or Buffer
Secret used to encrypt data. If string
is provided it is converted into Buffer
internally using provided encoding.
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.
Type: constants.ENCODING
Default: constants.ENCODING.utf8
Encoding used to convert between strings and Buffers. For most cases utf8
is suitable.
Type: number
Default: 1514764800
Used to delay year 2038 problem. Should never be changed.
Necessary since timestamp is stored as 4 bytes.
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.
See Crypter below
See Cryper below
See Cryper below
General Encryption Error that all Encryption Errors inherit from.
Thrown from lockObj
when JSON.stringify
fails.
General Decryption Error that all Decryption Errors inherit from.
Provided data can not be decrypted. Can be indication for invalid data or incorrect secret or name.
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.
The chest is not valid yet. This usually only happens when the zeroTime is changed.
The chest has expired.
Thrown from unlockObj
when JSON.parse
fails.
lock(treasure, ...contexts)
Create and "lock" new chest. Takes data to encrypt as first argument and contexts as additional arguments.
When unlocking chest where contexts have been provided to lock it, unlocking requires the contexts to be identical.
Wraps lock
, and JSON.stringify
is applied to first argument. On failure EncryptionJsonError
is thrown.
unlock(chest, ...contexts)
Unlock a chest and returns data. Takes data to decrypt as first argument and contexts as additional arguments.
This method can throw various errors (see section).
Wraps unlock
, and JSON.parse
is applied to return value. On failure DecryptionJsonError
is thrown.
Exposes the underlying Crypter. Useful for debugging.
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"
Used to encrypt and decrypt data using aes-256-cbc
with 16
bit random IV by default (see notes below).
Deals only with Buffers and produced web-safe base64 and hence is encoding independent.
Internally this uses GZip when this shortens the output.
Important: Errors are not explicitly handled.
encrypt(Buffer)
Takes a Buffer and encrypts it as a web-safe base64 encoded string.
decrypt(Base64)
Takes a web-safe base64 encoded string and decrypts it into a Buffer.
Type: Buffer
Secret used to encrypt data. Internally this gets hashed.
Type: constant.GZIP_MODE
Default: constant.GZIP_MODE.AUTO
Overwrite gzip mode. By default gzip mode is only used when output is shortened. Useful when gzip is computationally too expensive.
Will not change how first bit is set in IV and hence ok to change for existing tokens.
Type: string
Default: aes-256-cbc
Defines encryption algorithm. IV length must be compatible.
Type: number
Default: 16
Defines length of IV. Must be compatible with encryption.
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
Values AUTO
, NEVER
, FORCE
Defines gzip mode used internally.
Values utf8
, ascii
, latin1
, binary
Defines encoding for string to buffer conversions.
The functions toUrlSafeBase64
and fromUrlSafeBase64
are exposed.
toUrlSafeBase64(Buffer)
fromUrlSafeBase64(Base64)
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.
GZip is only used when this shortens the output. One bit in IV indicates this and hence only len - 1
bits are truly random. Acceptable when len(IV) >= 16
bytes.