npm package: @godspeedsystems/prisma-deterministic-search-field-encryption
Description: Transparent and customizable field-level encryption at rest for Prisma based on prisma-field-encryption package
Author: Anfitrião Prime
Latest version: 0.0.7

<h1 align="center"><code>prisma-deterministic-search-field-encryption</code></h1>

<div align="center">

[![NPM](https://img.shields.io/npm/v/prisma-field-encryption?color=red)](https://www.npmjs.com/package/prisma-field-encryption)
[![MIT License](https://img.shields.io/github/license/47ng/prisma-field-encryption.svg?color=blue)](https://github.com/47ng/prisma-field-encryption/blob/main/LICENSE)
[![Continuous Integration](https://github.com/47ng/prisma-field-encryption/workflows/Continuous%20Integration/badge.svg?branch=next)](https://github.com/47ng/prisma-field-encryption/actions)
[![Coverage Status](https://coveralls.io/repos/github/47ng/prisma-field-encryption/badge.svg?branch=next)](https://coveralls.io/github/47ng/prisma-field-encryption?branch=next)

</div>

<p align="center">Transparent and customizable field-level encryption at rest for Prisma using deterministic static algorithm based on  <a href="https://github.com/Vitu-77/prisma-field-encryption" target="_blank">prisma-custom-field-encryption</a> package. You can also perform queries over encrypted fields. </p>

## Installation

```shell
$ yarn add @mindgrep/prisma-deterministic-search-field-encryption
# or
$ npm i @mindgrep/prisma-deterministic-search-field-encryption
```

> _Note: this requires Prisma 3.8.0 or higher._

## Usage

### 1. Add the middleware to your Prisma client

```ts
import { PrismaClient } from '@prisma/client'
import { fieldEncryptionMiddleware } from '@mindgrep/prisma-deterministic-search-field-encryption'

export const client = new PrismaClient()

// This is a function, don't forget to call it:
client.$use(fieldEncryptionMiddleware())
```

_Tip: place the middleware as low as you need cleartext data._

_Any middleware registered after field encryption will receive encrypted data for the selected fields._

### 2. Setup your configuration

You can use your own encrypt/decript functions and logic:

#### 2.1. Using your own encrypt/decript functions

You must define your encryp/decrypt functions and pass then directly in the middleware config.

The following example shows using the native nodejs crypto module to perform encryption and decryption:

```ts
import crypto from 'crypto'

function cipher(decrypted: unknown): string {
  const cipher = crypto.createCipheriv(
    'aes-256-gcm',
    process.env.CRYPTO_SALT,
    process.env.CRYPTO_IV
  )
  return cipher.update(decrypted, 'utf-8', 'hex')
}

function decipher(encrypted: string): unknown {
  const decipher = crypto.createDecipheriv(
    'aes-256-gcm',
    process.env.CRYPTO_SALT,
    process.env.CRYPTO_IV
  )
  return decipher.update(encrypted, 'hex', 'utf-8')
}

client.$use(
  fieldEncryptionMiddleware({
    encryptFn: (decrypted: unknown) => cipher(decrypted),
    decryptFn: (encrypted: string) => decipher(encrypted)
  })
)
```

> _Note: a valid encrypt function must always receive a value(it can be any valid DB data) and return a encrypted string. The opposite is valid for the decryption function._

### 3. Annotate your schema

In your Prisma schema, add `/// @encrypted` to the fields you want to encrypt:

```prisma
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String? /// @encrypted <- annotate fields to encrypt
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
  authorId  Int?
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String? /// @encrypted <- can be optional
  posts Post[]
}
```

_Tip: make sure you use a triple-slash. Double slash comments won't work._

### 4. Regenerate your client

Make sure you have a generator for the Prisma client:

```prisma
generator client {
  provider = "prisma-client-js"
}
```

Then generate it using the `prisma` CLI:

```shell
$ prisma generate
```

You're done!

## Custom Prisma Client Location
If you are generating your Prisma client to a custom location, you'll need to
tell the middleware where to look for the DMMF _(the internal AST generated by Prisma that we use to read those triple-slash comments)_:
```ts
import { Prisma } from '../my/prisma/client'
prismaClient.$use(
  fieldEncryptionMiddleware({
    dmmf: Prisma.dmmf
  })
)
```
## Caveats & Limitations

You can only encrypt `String` fields.

[Raw database access](https://www.prisma.io/docs/concepts/components/prisma-client/raw-database-access)
operations are not supported.

Adding encryption adds overhead, both in storage space and in time to run queries,
though its impact hasn't been measured yet.

## How Does This Work ?

The middleware reads the Prisma AST (DMMF) to find annotations (only triple-slash
comments make it there) and build a list of encrypted Model.field pairs.

When a query is received, if there's input data to encrypt (write operations),
the relevant fields are encrypted. Then the encrypted data is sent to the
database.

Data returned from the database is scanned for encrypted fields, and those are
attempted to be decrypted. Errors will be logged and any unencrypted data will
be passed through, allowing seamless setup.

The generated data migrations files iterate over models that contain encrypted
fields, record by record, using the `interactiveTransaction` preview feature to
ensure that a record is not overwritten by other concurrent updates.

Because of the transparent encryption provided by the middleware, iterating over
records looks like a no-op (reading then updating with the same data), but this
will take care of:

- Encrypting fields newly `/// @encrypted`
- Rotating the encryption key when it changed
- Decrypting fields where encryption is being disabled with `/// @encrypted?readonly`. Once that migration has run, you can remove the annotation on those fields.

## Do I Need This ?

Some data is sensitive, and it's easy to give read access to the database to
a contractor or have backups end up somewhere they shouldn't be.

For those cases, encrypting the data per-field can make sense.

An example use-case is Two Factor authentication TOTP secrets: your app needs
them to authenticate your users, but nobody else should have access to them.

## Cryptography

Cipher used: AES-GCM with 256 bit keys.

## Obligatory Disclaimer About Passwords

🚨 **DO NOT USE THIS TO ENCRYPT PASSWORDS WITHOUT ADDITIONAL SECURITY MEASURES** 🚨

Passwords should be hashed & salted using a slow, constant-time one-way function. However, this library could be used to encrypt the salted and hashed password as a [pepper](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#peppering) to provide an additional layer of security. It is recommended that the encryption key be stored in a [Hardware Security Module](https://en.wikipedia.org/wiki/Hardware_security_module) on the server.

For hashing passwords, don't reinvent the wheel: use Argon2id if you can, otherwise scrypt.

## License

[MIT](./LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com)

Using this package at work ? [Sponsor me](https://github.com/sponsors/franky47) to help with support and maintenance.