Skip to main content

@firebase-backup-platform/encryption

The encryption package provides secure AES-256-GCM encryption for FireBackup. It handles key management, encryption/decryption operations, and secure key derivation.

Installation

npm install @firebase-backup-platform/encryption
# or
yarn add @firebase-backup-platform/encryption

Overview

This package implements industry-standard encryption:

  • AES-256-GCM - Authenticated encryption with associated data (AEAD)
  • Random IV - Unique initialization vector per encryption operation
  • Authentication tags - Integrity verification for encrypted data
  • Key derivation - PBKDF2-HMAC-SHA256 for password-based keys

Quick Start

Basic Encryption

import { encrypt, decrypt, generateKey } from '@firebase-backup-platform/encryption';

// Generate a new encryption key
const key = generateKey();
console.log('Key (store securely):', key.toString('base64'));

// Encrypt data
const plaintext = Buffer.from('Sensitive backup data');
const encrypted = await encrypt(plaintext, key);

console.log('Ciphertext:', encrypted.ciphertext.toString('base64'));
console.log('IV:', encrypted.iv.toString('base64'));
console.log('Auth Tag:', encrypted.authTag.toString('base64'));

// Decrypt data
const decrypted = await decrypt(encrypted, key);
console.log('Decrypted:', decrypted.toString());

Using the EncryptionService

import { EncryptionService } from '@firebase-backup-platform/encryption';

const encryptionService = new EncryptionService({
key: process.env.ENCRYPTION_KEY
});

// Encrypt
const encrypted = await encryptionService.encrypt(data);

// Decrypt
const decrypted = await encryptionService.decrypt(encrypted);

API Reference

Key Generation

generateKey()

Generates a cryptographically secure 256-bit key.

function generateKey(): Buffer

Example:

import { generateKey } from '@firebase-backup-platform/encryption';

const key = generateKey();

// Store as base64 string
const keyString = key.toString('base64');
console.log('Key:', keyString); // Store in secrets manager

// Load from base64 string
const loadedKey = Buffer.from(keyString, 'base64');

deriveKey(password, salt, options)

Derives a key from a password using PBKDF2.

function deriveKey(
password: string,
salt: Buffer | string,
options?: KeyDerivationOptions
): Promise<Buffer>

KeyDerivationOptions:

PropertyTypeDefaultDescription
iterationsnumber100000PBKDF2 iterations
keyLengthnumber32Output key length (bytes)
digeststring'sha256'Hash algorithm

Example:

import { deriveKey, generateSalt } from '@firebase-backup-platform/encryption';

// Generate a random salt (store alongside encrypted data)
const salt = generateSalt();

// Derive key from password
const key = await deriveKey('user-password', salt, {
iterations: 100000
});

// Store salt with encrypted data for decryption
console.log('Salt:', salt.toString('base64'));

generateSalt(length)

Generates a random salt for key derivation.

function generateSalt(length?: number): Buffer

Example:

const salt = generateSalt(16);  // 16 bytes
console.log('Salt:', salt.toString('hex'));

Encryption/Decryption

encrypt(data, key, options)

Encrypts data using AES-256-GCM.

function encrypt(
data: Buffer | string,
key: Buffer | string,
options?: EncryptOptions
): Promise<EncryptedData>

EncryptOptions:

PropertyTypeDefaultDescription
aadBuffer-Additional authenticated data
encodingstring'utf8'String encoding

EncryptedData:

interface EncryptedData {
ciphertext: Buffer;
iv: Buffer; // 12 bytes
authTag: Buffer; // 16 bytes
aad?: Buffer;
}

Example:

const encrypted = await encrypt(
Buffer.from('Sensitive data'),
key,
{ aad: Buffer.from('backup-metadata') }
);

decrypt(encryptedData, key)

Decrypts data encrypted with AES-256-GCM.

function decrypt(
encryptedData: EncryptedData,
key: Buffer | string
): Promise<Buffer>

Example:

const decrypted = await decrypt(
{
ciphertext: encrypted.ciphertext,
iv: encrypted.iv,
authTag: encrypted.authTag,
aad: encrypted.aad
},
key
);

console.log('Decrypted:', decrypted.toString());

EncryptionService Class

A high-level service for encryption operations.

Constructor

new EncryptionService(config: EncryptionServiceConfig)

EncryptionServiceConfig:

PropertyTypeRequiredDescription
keystring | BufferYesEncryption key
keyDerivationKeyDerivationConfigNoPassword-based key derivation

Example:

// Using a pre-generated key
const service = new EncryptionService({
key: process.env.ENCRYPTION_KEY
});

// Using password-based key derivation
const service = new EncryptionService({
key: 'master-password',
keyDerivation: {
enabled: true,
salt: process.env.KEY_SALT,
iterations: 100000
}
});

Methods

encrypt(data, options)
async encrypt(data: Buffer | string, options?: EncryptOptions): Promise<EncryptedPayload>

EncryptedPayload:

interface EncryptedPayload {
data: Buffer; // IV + Ciphertext + AuthTag combined
metadata: {
algorithm: string;
ivLength: number;
authTagLength: number;
};
}
decrypt(payload)
async decrypt(payload: EncryptedPayload): Promise<Buffer>
encryptStream(inputStream, outputStream)

Encrypts a stream of data.

async encryptStream(
inputStream: Readable,
outputStream: Writable
): Promise<StreamEncryptionResult>
decryptStream(inputStream, outputStream)

Decrypts a stream of data.

async decryptStream(
inputStream: Readable,
outputStream: Writable
): Promise<void>

Serialization

Pack and Unpack

Helper functions for serializing encrypted data:

import { pack, unpack } from '@firebase-backup-platform/encryption';

// Pack encrypted data into a single buffer
const encrypted = await encrypt(data, key);
const packed = pack(encrypted);

console.log('Packed length:', packed.length);

// Unpack to get individual components
const unpacked = unpack(packed);
const decrypted = await decrypt(unpacked, key);

Format

The packed format is:

+--------+------------+----------+
| IV | AuthTag | Ciphertext|
| 12 B | 16 B | Variable |
+--------+------------+----------+

JSON Serialization

import { toJSON, fromJSON } from '@firebase-backup-platform/encryption';

// Serialize to JSON-safe format
const encrypted = await encrypt(data, key);
const json = toJSON(encrypted);

console.log(json);
// {
// ciphertext: "base64...",
// iv: "base64...",
// authTag: "base64..."
// }

// Deserialize
const restored = fromJSON(json);
const decrypted = await decrypt(restored, key);

Stream Encryption

For large files, use streaming encryption:

import { createEncryptStream, createDecryptStream } from '@firebase-backup-platform/encryption';
import { pipeline } from 'stream/promises';
import fs from 'fs';

// Encrypt a file
const encryptStream = createEncryptStream(key);
await pipeline(
fs.createReadStream('large-backup.json'),
encryptStream,
fs.createWriteStream('large-backup.encrypted')
);

// Get encryption metadata (needed for decryption)
const { iv, authTag } = encryptStream.getMetadata();

// Decrypt a file
const decryptStream = createDecryptStream(key, { iv, authTag });
await pipeline(
fs.createReadStream('large-backup.encrypted'),
decryptStream,
fs.createWriteStream('large-backup.decrypted.json')
);

Key Rotation

Support for key rotation:

import { KeyRotator } from '@firebase-backup-platform/encryption';

const rotator = new KeyRotator({
currentKey: process.env.CURRENT_ENCRYPTION_KEY,
previousKeys: [
process.env.PREVIOUS_KEY_1,
process.env.PREVIOUS_KEY_2
]
});

// Decrypt with any key (tries current, then previous)
const decrypted = await rotator.decrypt(encryptedData);

// Re-encrypt with current key
const reencrypted = await rotator.reencrypt(encryptedData);

// Check if data needs re-encryption
const needsRotation = await rotator.needsReencryption(encryptedData);

Error Handling

import {
encrypt,
decrypt,
EncryptionError,
DecryptionError,
InvalidKeyError,
AuthenticationError,
KeyDerivationError
} from '@firebase-backup-platform/encryption';

try {
const decrypted = await decrypt(encryptedData, key);
} catch (error) {
if (error instanceof InvalidKeyError) {
console.error('Invalid encryption key format');
} else if (error instanceof AuthenticationError) {
console.error('Data integrity check failed - data may be tampered');
} else if (error instanceof DecryptionError) {
console.error('Decryption failed:', error.message);
} else if (error instanceof EncryptionError) {
console.error('Encryption error:', error.message);
}
}

Security Specifications

Algorithm Details

PropertyValue
AlgorithmAES-256-GCM
Key Size256 bits (32 bytes)
IV Size96 bits (12 bytes)
Auth Tag Size128 bits (16 bytes)
ModeGalois/Counter Mode (GCM)

Key Derivation (PBKDF2)

PropertyDefault Value
AlgorithmPBKDF2
HashHMAC-SHA256
Iterations100,000
Salt Size128 bits (16 bytes)
Key Length256 bits (32 bytes)

Best Practices

Security Best Practices

  • Generate unique IV for each encryption operation

    • Never reuse IV with the same key
  • Store keys in a secrets manager

    • AWS Secrets Manager
    • HashiCorp Vault
    • Google Cloud Secret Manager
  • Rotate keys periodically

    • Recommended: annually
  • Verify auth tag before processing decrypted data

    • Prevents tampering attacks
  • Use secure random for key generation

    • crypto.randomBytes() in Node.js
  • Keep backups of encryption keys

    • Separate from encrypted data

TypeScript Support

import {
encrypt,
decrypt,
generateKey,
deriveKey,
generateSalt,
pack,
unpack,
toJSON,
fromJSON,
EncryptionService,
KeyRotator,
createEncryptStream,
createDecryptStream,
EncryptedData,
EncryptedPayload,
EncryptOptions,
KeyDerivationOptions,
EncryptionServiceConfig,
EncryptionError,
DecryptionError,
InvalidKeyError,
AuthenticationError
} from '@firebase-backup-platform/encryption';

// Typed operations
const key: Buffer = generateKey();
const encrypted: EncryptedData = await encrypt(data, key);
const decrypted: Buffer = await decrypt(encrypted, key);

// Typed service
const config: EncryptionServiceConfig = {
key: process.env.ENCRYPTION_KEY!
};
const service = new EncryptionService(config);

Integration with Backup Core

import { BackupCore } from '@firebase-backup-platform/backup-core';
import { generateKey } from '@firebase-backup-platform/encryption';

// Generate a key (store securely!)
const encryptionKey = generateKey();

const backupCore = new BackupCore({
firebase: { ... },
storage: { ... },
encryption: {
enabled: true,
key: encryptionKey
}
});

// All backups are now encrypted with AES-256-GCM
const result = await backupCore.backup({
collections: ['users']
});