Skip to content

Repository files navigation

@boringnode/encryption

typescript-imagegh-workflow-imagenpm-imagenpm-download-imagelicense-image

A framework-agnostic encryption library for Node.js. Built with simplicity and security in mind, @boringnode/encryption provides a unified API for encrypting and signing data with support for multiple encryption algorithms and key rotation.

Installation

npm install @boringnode/encryption

Features

  • Multiple Algorithms: ChaCha20-Poly1305, AES-256-GCM, AES-256-CBC, AES-SIV
  • Key Rotation: Encrypt with new keys, decrypt with old ones
  • Deterministic Encryption: AES-SIV driver for equality queries
  • Purpose-Bound Encryption: Ensure encrypted values are used for their intended purpose
  • Expiration Support: Set time-to-live on encrypted values
  • Blind Indexes: Deterministic indexes for equality queries
  • Message Verification: Sign data without encrypting (HMAC-based)
  • Type-Safe: Full TypeScript support with typed payloads

Quick Start

1. Configure Encryption

import{Encryption}from'@boringnode/encryption'import{chacha20poly1305}from'@boringnode/encryption/drivers/chacha20_poly1305'constencryption=newEncryption(chacha20poly1305({id: 'app',keys: [process.env.APP_KEY],}))

id must be a non-empty string and cannot contain ..

2. Encrypt & Decrypt

// Encrypt any valueconstencrypted=encryption.encrypt({userId: 1,role: 'admin'})// => "app.base64EncodedCipherText.base64EncodedIv.base64EncodedTag"// Decrypt the valueconstdecrypted=encryption.decrypt(encrypted)// => { userId: 1, role: 'admin' }

Supported Data Types

The library supports encrypting a wide range of data types:

  • Strings
  • Numbers
  • Booleans
  • Arrays
  • Objects
  • Dates

Encryption Drivers

ChaCha20-Poly1305 (recommended)

Modern, fast, and secure. Recommended for most use cases.

import{chacha20poly1305}from'@boringnode/encryption/drivers/chacha20_poly1305'constconfig=chacha20poly1305({id: 'app',keys: ['your-32-character-secret-key-here'],})

AES-256-GCM

Industry-standard authenticated encryption.

import{aes256gcm}from'@boringnode/encryption/drivers/aes_256_gcm'constconfig=aes256gcm({id: 'app',keys: ['your-32-character-secret-key-here'],})

AES-256-CBC

Legacy support with HMAC authentication.

import{aes256cbc}from'@boringnode/encryption/drivers/aes_256_cbc'constconfig=aes256cbc({id: 'app',keys: ['your-32-character-secret-key-here'],})

AES-SIV (deterministic)

Deterministic encryption for direct equality lookups on encrypted columns.

import{aessiv}from'@boringnode/encryption/drivers/aes_siv'constconfig=aessiv({id: 'app',key: 'your-32-character-secret-key-here',})

Notes:

  • expiresIn is not supported with deterministic encryption.
  • Key rotation is not automatic for deterministic ciphertexts. Use an explicit migration/backfill strategy.

Key Rotation

The library supports multiple keys for seamless key rotation. The first key is used for encryption, while all keys are tried during decryption.

constencryption=newEncryption(chacha20poly1305({id: 'app',keys: [process.env.NEW_APP_KEY,// Used for encryptionprocess.env.OLD_APP_KEY,// Still valid for decryption],}))// New encryptions use NEW_APP_KEYconstencrypted=encryption.encrypt('secret')// Decryption works with both keysencryption.decrypt(encryptedWithOldKey)// Worksencryption.decrypt(encryptedWithNewKey)// Works

Purpose-Bound Encryption

Ensure encrypted values are only used for their intended purpose:

// Encrypt with a purposeconsttoken=encryption.encrypt({userId: 1},undefined,'password-reset')// Must provide same purpose to decryptencryption.decrypt(token,'password-reset')// => { userId: 1 }encryption.decrypt(token,'email-verify')// => nullencryption.decrypt(token)// => null

Expiration Support

Set a time-to-live on encrypted values:

// Expires in 1 hourconsttoken=encryption.encrypt({userId: 1},'1h')// Expires in 30 minutesconsttoken=encryption.encrypt({userId: 1},'30m')// Expires in 7 daysconsttoken=encryption.encrypt({userId: 1},'7d')// After expiration, decrypt returns nullencryption.decrypt(expiredToken)// => null

Blind Indexes

Blind indexes are deterministic hashes used for equality queries:

constindex=encryption.blindIndex('foo@example.com','users.email')

When rotating keys, query using all blind indexes:

constindexes=encryption.blindIndexes('foo@example.com','users.email')// Use SQL: WHERE email_index IN (...)

Rules:

  • purpose is required and should identify the field/context (users.email, users.ssn, ...).
  • Matching is exact-bytes (no implicit normalization).
  • Prefer normalized primitive values for blind indexes (string/number/boolean/ISO date).
  • For structured objects, normalize/canonicalize before indexing (for example, map object -> stable string yourself).
constemailIndex=encryption.blindIndex(email.trim().toLowerCase(),'users.email')

Message Verifier

When you need to ensure data integrity without hiding the content, use the MessageVerifier. The payload is base64-encoded (not encrypted) and signed with HMAC.

import{MessageVerifier}from'@boringnode/encryption/message_verifier'constverifier=newMessageVerifier(['your-32-character-secret-key-here'])// Sign a valueconstsigned=verifier.sign({userId: 1})// Verify and retrieve the valueconstpayload=verifier.unsign(signed)// => { userId: 1 }// Tampered values return nullverifier.unsign('tampered.value')// => null

The verifier also supports purpose and expiration:

// With expirationconstsigned=verifier.sign({userId: 1},'1h')// With purposeconstsigned=verifier.sign({userId: 1},undefined,'api-token')constpayload=verifier.unsign(signed,'api-token')

Base64 Utilities

URL-safe base64 encoding/decoding utilities are available:

import{base64UrlEncode,base64UrlDecode}from'@boringnode/encryption/base64'constencoded=base64UrlEncode('Hello World')constdecoded=base64UrlDecode(encoded,'utf8')

HMAC

Generate and verify HMAC signatures:

import{Hmac}from'@boringnode/encryption'consthmac=newHmac(secretKey)// Generate HMACconsthash=hmac.generate('data to sign')// Verify HMAC (timing-safe comparison)constisValid=hmac.compare('data to sign',hash)

Error Handling

The library is designed to return null on decryption failures rather than throwing exceptions. This prevents timing attacks and simplifies error handling:

constresult=encryption.decrypt(maybeInvalidValue)if(result===null){// Invalid, expired, wrong purpose, or tampered}

About

A framework agnostic encryption library

Topics

Resources

Stars

16 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages