Encryption & Hashing
Guren provides utilities for encrypting data and hashing passwords securely.
Encryption & Hashing
Guren provides utilities for encrypting data and hashing passwords securely.
APP_KEY
Every Guren application needs an APP_KEY — a base64-encoded 32-byte secret used for encryption, cookie signing, and token signing. Guren uses HKDF to derive separate keys for each purpose, so a single APP_KEY secures all subsystems without sharing raw key material.
Generating a Key
# Generate and print a key
bunx guren key:generate
# Generate and write directly to .env
bunx guren key:generate --write
create-guren-app generates an APP_KEY automatically when scaffolding a new project.
Key Rotation
To rotate your APP_KEY without breaking existing encrypted data or active sessions:
- Move the current
APP_KEYvalue toAPP_PREVIOUS_KEYS - Generate a new
APP_KEY
# .env
APP_KEY=base64:<new-key>
APP_PREVIOUS_KEYS=base64:<old-key>
Multiple previous keys can be comma-separated. Guren will try the current key first, then fall back to previous keys when decrypting data or verifying signatures.
Encryption
The Encrypter class provides AES-256-GCM encryption for sensitive data.
Setup
Create an encrypter with a 32-byte key:
import { Encrypter, generateKey } from '@guren/core'
// Generate a new key
const key = generateKey()
console.log(key) // base64:... (32-byte key)
// Create encrypter
const encrypter = new Encrypter({ key })
// With key rotation support
const rotatedEncrypter = new Encrypter({
key: newKey,
previousKeys: [oldKey],
})
Encrypting Data
// Encrypt any value (objects are JSON-serialized automatically)
const encrypted = encrypter.encrypt({ userId: 1, token: 'abc123' })
// Encrypt a raw string without serialization
const encryptedString = encrypter.encryptString('secret message')
Decrypting Data
// Decrypt (automatically deserializes JSON)
const data = encrypter.decrypt(encrypted)
// Returns: { userId: 1, token: 'abc123' }
// Decrypt a raw string
const message = encrypter.decryptString(encryptedString)
// Returns: 'secret message'
Key Management
import { generateKey, Encrypter } from '@guren/core'
// Generate a cryptographically secure key
const key = generateKey()
// Get the current key
const currentKey = encrypter.getKey()
Store your encryption key securely in environment variables:
# .env
APP_KEY=base64:your-32-byte-key-here
Error Handling
import { Encrypter } from '@guren/core'
try {
const decrypted = encrypter.decrypt(invalidPayload)
} catch (error) {
console.error('Decryption failed:', (error as Error).message)
}
Hashing
The Hash class provides secure password hashing using bcrypt, argon2, or scrypt algorithms.
Creating a Hasher
import { Hash } from '@guren/core'
// Default bcrypt hasher
const hash = new Hash()
// Argon2 hasher
const argon2Hash = new Hash({ driver: 'argon2' })
// Scrypt hasher
const scryptHash = new Hash({ driver: 'scrypt' })
// Bcrypt with custom rounds
const bcryptHash = new Hash({ driver: 'bcrypt', rounds: 12 })
Hashing Passwords
const hash = new Hash()
// Hash a password
const hashedPassword = await hash.make('user-password')
// Returns: $2b$10$...
// Hash is async for security (uses worker threads)
Verifying Passwords
// Check if a password matches
const isValid = await hash.check('user-password', hashedPassword)
// Returns: true or false
Checking If Rehash Needed
// Check if hash needs to be rehashed (e.g., rounds changed)
const needsRehash = hash.needsRehash(hashedPassword)
if (needsRehash) {
const newHash = await hash.make(plainPassword)
await user.update({ password: newHash })
}
Getting Hash Info
const info = hash.info(hashedPassword)
// Returns: { algorithm: 'bcrypt', options: { rounds: 10 } }
Algorithm Options
Bcrypt (Default)
const hash = new Hash({
driver: 'bcrypt',
rounds: 10, // Cost factor (default: 10, recommended: 10-12)
})
Argon2
const hash = new Hash({
driver: 'argon2',
memoryCost: 65536, // Memory usage in KB (default: 65536)
timeCost: 3, // Iterations (default: 3)
parallelism: 4, // Parallel threads (default: 4)
type: 'argon2id', // 'argon2i', 'argon2d', or 'argon2id' (default)
})
Scrypt
const hash = new Hash({
driver: 'scrypt',
cost: 16384, // CPU/memory cost (N)
blockSize: 8, // Block size (r)
parallelization: 1, // Parallelization (p)
keyLength: 64, // Output length
})
Using in Controllers
import { Controller, Hash } from '@guren/core'
export default class AuthController extends Controller {
private hash = new Hash()
async register() {
const { email, password } = await this.request.json()
const user = await User.create({
email,
password: await this.hash.make(password),
})
return this.json({ user })
}
async login() {
const { email, password } = await this.request.json()
const user = await User.findBy('email', email)
if (!user || !await this.hash.check(password, user.password)) {
return this.json({ error: 'Invalid credentials' }, 401)
}
// Check if rehash needed
if (this.hash.needsRehash(user.password)) {
await user.update({
password: await this.hash.make(password),
})
}
return this.json({ user })
}
}
Security Best Practices
- Never store plain passwords — Always hash passwords before storing.
- Use a strong APP_KEY — Run
bunx guren key:generate --writeto generate one. Never commit it to version control. - Don't roll your own crypto — Use the provided utilities.
- Rotate keys periodically — Use
APP_PREVIOUS_KEYSto rotate without downtime (see Key Rotation). - Use Scrypt for new projects — It's Guren's default and recommended password hashing algorithm.
Testing
import { describe, it, expect } from 'bun:test'
import { Encrypter, Hash, generateKey } from '@guren/core'
describe('Encryption', () => {
it('encrypts and decrypts data', () => {
const encrypter = new Encrypter({ key: generateKey() })
const encrypted = encrypter.encrypt('secret')
const decrypted = encrypter.decrypt(encrypted)
expect(decrypted).toBe('secret')
})
})
describe('Hashing', () => {
it('hashes and verifies passwords', async () => {
const hash = new Hash()
const hashed = await hash.make('password123')
const valid = await hash.check('password123', hashed)
expect(valid).toBe(true)
})
it('rejects invalid passwords', async () => {
const hash = new Hash()
const hashed = await hash.make('password123')
const valid = await hash.check('wrong-password', hashed)
expect(valid).toBe(false)
})
})