Skip to content

boringnode/encryption

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@boringnode/encryption

typescript-image gh-workflow-image npm-image npm-download-image license-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
  • Key Rotation: Encrypt with new keys, decrypt with old ones
  • Purpose-Bound Encryption: Ensure encrypted values are used for their intended purpose
  • Expiration Support: Set time-to-live on encrypted values
  • 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'

const encryption = new Encryption(
  chacha20poly1305({
    id: 'app',
    keys: [process.env.APP_KEY],
  })
)

2. Encrypt & Decrypt

// Encrypt any value
const encrypted = encryption.encrypt({ userId: 1, role: 'admin' })
// => "app.base64EncodedCipherText.base64EncodedIv.base64EncodedTag"

// Decrypt the value
const decrypted = 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'

const config = 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'

const config = 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'

const config = aes256cbc({
  id: 'app',
  keys: ['your-32-character-secret-key-here'],
})

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.

const encryption = new Encryption(
  chacha20poly1305({
    id: 'app',
    keys: [
      process.env.NEW_APP_KEY, // Used for encryption
      process.env.OLD_APP_KEY, // Still valid for decryption
    ],
  })
)

// New encryptions use NEW_APP_KEY
const encrypted = encryption.encrypt('secret')

// Decryption works with both keys
encryption.decrypt(encryptedWithOldKey) // Works
encryption.decrypt(encryptedWithNewKey) // Works

Purpose-Bound Encryption

Ensure encrypted values are only used for their intended purpose:

// Encrypt with a purpose
const token = encryption.encrypt({ userId: 1 }, undefined, 'password-reset')

// Must provide same purpose to decrypt
encryption.decrypt(token, 'password-reset') // => { userId: 1 }
encryption.decrypt(token, 'email-verify')   // => null
encryption.decrypt(token)                    // => null

Expiration Support

Set a time-to-live on encrypted values:

// Expires in 1 hour
const token = encryption.encrypt({ userId: 1 }, '1h')

// Expires in 30 minutes
const token = encryption.encrypt({ userId: 1 }, '30m')

// Expires in 7 days
const token = encryption.encrypt({ userId: 1 }, '7d')

// After expiration, decrypt returns null
encryption.decrypt(expiredToken) // => null

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'

const verifier = new MessageVerifier(['your-secret-key'])

// Sign a value
const signed = verifier.sign({ userId: 1 })

// Verify and retrieve the value
const payload = verifier.unsign(signed)
// => { userId: 1 }

// Tampered values return null
verifier.unsign('tampered.value') // => null

The verifier also supports purpose and expiration:

// With expiration
const signed = verifier.sign({ userId: 1 }, '1h')

// With purpose
const signed = verifier.sign({ userId: 1 }, undefined, 'api-token')
const payload = verifier.unsign(signed, 'api-token')

Base64 Utilities

URL-safe base64 encoding/decoding utilities are available:

import { base64UrlEncode, base64UrlDecode } from '@boringnode/encryption/base64'

const encoded = base64UrlEncode('Hello World')
const decoded = base64UrlDecode(encoded, 'utf8')

HMAC

Generate and verify HMAC signatures:

import { Hmac } from '@boringnode/encryption'

const hmac = new Hmac(secretKey)

// Generate HMAC
const hash = hmac.generate('data to sign')

// Verify HMAC (timing-safe comparison)
const isValid = 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:

const result = encryption.decrypt(maybeInvalidValue)

if (result === null) {
  // Invalid, expired, wrong purpose, or tampered
}