Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

Security

Encryption

Encrypt values your app stores or sends, such as another service's API token, with your app's key, and decrypt them when you need them back.

Introduction#

Oriel's encrypter uses AES-256 or AES-128, through Node's own crypto module. Every value it encrypts is authenticated, so a value that was changed after it was encrypted can't be decrypted. Your app's cookies, its sessions when session.encrypt is on, and the Crypt facade all use it.

Configuration#

The encrypter's key is key in config/app.ts, which reads APP_KEY from .env. The installer generates it, and node oriel key:generate writes a new one: a random key for your cipher, written as base64: and its bytes (see The application key).

cipher in config/app.ts names the cipher, AES-256-GCM in a new app. AES-128-GCM, AES-256-CBC and AES-128-CBC are the others, in upper or lower case. The key must have the length the cipher takes: 32 bytes for AES-256 and 16 for AES-128.

An app without a key fails the first time it needs the encrypter, such as on its first request to a page of the web group, whose cookies it encrypts, with "No application encryption key has been specified.".

Gracefully rotating encryption keys#

A new key can't decrypt what the old one encrypted, so changing it signs every user out, as their session cookies no longer decrypt, and makes every value you encrypted with the old key unreadable. List your previous keys in APP_PREVIOUS_KEYS, separated by commas:

APP_KEY="base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY="APP_PREVIOUS_KEYS="base64:2nLsGFGzyoae2ax3EF2Lyq/hH6QghBGLIq5uL+Gp8/w="

Oriel then encrypts with the current key only, and decrypts with the current key or any previous one. Your users stay signed in, and the values you stored stay readable, while you encrypt them again with the new key.

Using the encrypter#

Encrypting a value#

The Crypt facade's encryptString() encrypts a string:

app/Http/Controllers/DigitalOceanTokenController.ts
import { Crypt, redirect, request } from '@orieljs/framework'import type { User } from '../../Models/User'import { Controller } from './Controller'export class DigitalOceanTokenController extends Controller {  /** Store a DigitalOcean API token for the user. */  async store() {    const user = (await request().user<User>())!    await user.forceFill({ token: Crypt.encryptString(request().string('token').toString()) }).save()    return redirect('/secrets')  }}

encrypt() takes any value that JSON can hold, such as an object, and turns it into JSON before it encrypts it. The encrypt() and decrypt() helpers do the same as the facade's methods:

const payload = Crypt.encrypt({ userId: 1, plan: 'pro' })const sameKind = encrypt({ userId: 1, plan: 'pro' })

Decrypting a value#

decryptString() decrypts what encryptString() encrypted, and decrypt() what encrypt() did, giving back the value. A value that can't be decrypted, because it was changed, was encrypted with another key, or isn't a payload at all, throws a DecryptError:

try {  const decrypted = Crypt.decryptString(encryptedValue)} catch (error) {  if (!(error instanceof DecryptError)) throw error  // ...}

decrypt() takes the type of the value it gives back, which is a cast, not a check: Crypt.decrypt<{ userId: number; plan: string }>(payload).

The encrypter's methods are synchronous, and the facade and helpers need your app to be running, as in a request, a job or a command.

Privacy policy ·

esc