README.md

# Erebus

[![Elixir CI](https://github.com/venndr/erebus/actions/workflows/elixir.yml/badge.svg)](https://github.com/venndr/erebus/actions/workflows/elixir.yml)

Erebus is an implementation of the envelope encryption paradigm. For each encrypted struct, it's using a separate key - called DEK
(short for data encryption key). It's regenerated (hence re-encrypting fields) on each save - making key encryption barely needed.
During each encryption, DEK is encrypted using KEK (key encryption key). DEK is a symmetric key - we're using
Aes 256 with Galois mode with aead (which guarantees both security and integrity of the data). KEK is an asymmetric key - we're
using a public key for encryption (for performance reason when using external key storage) and private for decryption. Specific implementation
depends on the backend. Currently, we're providing three:

- `Erebus.KMS.Google` - which uses Google KMS key storage. That means that your private key never leaves Google infrastructure,
  which is the most secure option.
- `Erebus.KMS.Local` - which uses private/public key pair stored on your hard drive. Please note that it makes them prone to leakage
- `Erebus.KMS.Dummy` - which uses base64 as encryption for DEK. Never use it in production.

Please note that you need to provide config for the operations and call them, providing them for each call.

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `erebus` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:erebus, "~> 0.2.0-rc.1"}
  ]
end
```

## Usage

To use Erebus, you need to wrap it and provide your configuration to calls.

Put following module in your app:

```elixir
defmodule MyApp.Erebus do
  def encrypt(struct, handle, version) do
    opts = Application.get_env(:my_app, :erebus)

    Erebus.encrypt(struct, handle, version, opts)
  end

  def decrypt() do
    opts = Application.get_env(:my_app, :erebus)
  end
end
```

and for encryptable fields define protocol implementation:

```elixir
defimpl Erebus.Encryption do
  def encrypted_fields(_), do: [:first, :second]
end
```

and for that struct add fields named:

```elixir
first
second
first_encrypted
second_encrypted
first_hash
second_hash
dek
```

in the case of Ecto, they need to be defined as follows:

```elixir
use Erebus.Schema

embedded_schema "table" do
  hashed_encrypted_field(:first)
  hashed_encrypted_field(:second)
  data_encryption_key()
end
```

### Usage with local KMS adapter

Provide following values in config:

```elixir
config :my_app, :erebus, kms_backend: Erebus.KMS.Local, keys_base_path: "some_path", private_key_password: "1234"
```

And generate asymmetric key pairs in that folder.

### Usage with Google KMS adapter

Please add to your application Goth:

```
{:goth, "~> 1.3.0-rc.2"}
```

and start it in your application.ex:

```elixir
credentials =
  "GCP_KMS_CREDENTIALS_PATH"
  |> System.fetch_env!()
  |> File.read!()
  |> Jason.decode!()

scopes = ["https://www.googleapis.com/auth/cloudkms"]

source = {:service_account, credentials, scopes: scopes}

children = [
  {Goth, name: MyApp.Goth, source: source}
]
```

and provide name as one of the options to Erebus:

```elixir
config :my_app, :erebus,
  kms_backend: Erebus.KMS.Google,
  google_project: "someproject",
  google_region: "someregion",
  google_keyring: "some_keyring",
  google_goth: MyApp.Goth
```

Please note that if you're using Google KMS, your key must have access to the following roles:

- Cloud KMS CryptoKey Encrypter/Decrypter
- Cloud KMS CryptoKey Public Key Viewer

### Ecto usage full example

```elixir
defmodule EncryptedStuff do
    use Ecto.Schema
    import Ecto.Changeset
    use Erebus.Schema

    embedded_schema do
      hashed_encrypted_field(:first)
      hashed_encrypted_field(:second)
      data_encryption_key()
      field(:other, :string)
    end

    def changeset(stuff, attrs) do
      changes =
        stuff
        |> cast(attrs, [
          :first,
          :second
        ])

      encrypted_data =
        changes
        |> MyApp.Erebus.encrypt("handle", 1)

      Ecto.Changeset.change(changes, encrypted_data)
    end

    defimpl Erebus.Encryption do
      def encrypted_fields(_), do: [:first, :second]
    end
  end
```

If you don't need multiple encryption keys, provide at hard-coded in `MyApp.Erebus`.

Currently, we support only encoding / decoding data using Ecto changeset.