Skip to content

Commit

Permalink
[#93] Add option to specify iv length for AES.GCM
Browse files Browse the repository at this point in the history
This allows you to set a 12-byte IV as mentioned in #93, like so:

```elixir
config :my_app, MyApp.Vault,
  ciphers: [
    default: {
      Cloak.Ciphers.AES.GCM,
      tag: "AES.GCM.V1",
      key: Base.decode64!("your-key-here"),
      iv_length: 12
    }
  ]
```

In Cloak 2.0, one of the breaking changes will be to make `iv_length`
default to `12` instead of the current `16`.
  • Loading branch information
danielberkompas committed Feb 1, 2020
1 parent 501a5a7 commit adf5574
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 23 deletions.
7 changes: 6 additions & 1 deletion README.md
Expand Up @@ -49,7 +49,12 @@ MyApp.Vault.decrypt(ciphertext)
```elixir
config :my_app, MyApp.Vault,
ciphers: [
aes_gcm: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: <<...>>},
# In AES.GCM, it is important to specify 12-byte IV length for
# interoperability with other encryption software. See this GitHub issue
# for more details: https://github.com/danielberkompas/cloak/issues/93
#
# In Cloak 2.0, this will be the default iv length for AES.GCM.
aes_gcm: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: <<...>>, iv_length: 12},
aes_ctr: {Cloak.Ciphers.AES.CTR, tag: "AES.CTR.V1", key: <<...>>}
]
```
Expand Down
4 changes: 3 additions & 1 deletion config/test.exs
Expand Up @@ -5,7 +5,9 @@ config :cloak, Cloak.TestVault,
ciphers: [
default:
{Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1", key: Base.decode64!("3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE=")},
tag: "AES.GCM.V1",
key: Base.decode64!("3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE="),
iv_length: 12},
secondary:
{Cloak.Ciphers.AES.CTR,
tag: "AES.CTR.V1", key: Base.decode64!("o5IzV8xlunc0m0/8HNHzh+3MCBBvYZa0mv4CsZic5qI=")}
Expand Down
22 changes: 19 additions & 3 deletions guides/how_to/install.md
Expand Up @@ -6,7 +6,7 @@ This guide will walk you through installing Cloak in your project.

First, add `:cloak` to your dependencies in `mix.exs`:

{:cloak, "1.0.0"}
{:cloak, "1.0.2"}

Run `mix deps.get` to fetch the dependency.

Expand Down Expand Up @@ -36,7 +36,18 @@ its raw binary form.

config :my_app, MyApp.Vault,
ciphers: [
default: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: Base.decode64!("your-key-here")}
default: {
Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1",
key: Base.decode64!("your-key-here"),
# In AES.GCM, it is important to specify 12-byte IV length for
# interoperability with other encryption software. See this GitHub
# issue for more details:
# https://github.com/danielberkompas/cloak/issues/93
#
# In Cloak 2.0, this will be the default iv length for AES.GCM.
iv_length: 12
}
]

If you want to fetch keys from system vars, you should use the `init/1` callback
Expand All @@ -54,7 +65,12 @@ to configure the vault instead:
def init(config) do
config =
Keyword.put(config, :ciphers, [
default: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: decode_env!("CLOAK_KEY")}
default: {
Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1",
key: decode_env!("CLOAK_KEY"),
iv_length: 12
}
])

{:ok, config}
Expand Down
5 changes: 2 additions & 3 deletions guides/upgrading/0.6.x_to_0.7.x.md
Expand Up @@ -43,7 +43,7 @@ Notice that the `tag: "AES"` became `module_tag: "AES"` in the `:retired`
cipher configuration.

Alternatively, if your keys are stored in environment variables, you could
configure the vault using the `Cloak.Vault.init/1` callback:
configure the vault using the `init/1` callback:

defmodule MyApp.Vault do
use Cloak.Vault, otp_app: :my_app
Expand Down Expand Up @@ -100,7 +100,7 @@ And then replace `Cloak.EncryptedBinaryField` in your schema:
schema "users" do
field :name, MyApp.Encrypted.Binary,
field :encryption_version
end
end

Finally, you'd remove the `:encryption_version` field as it is no longer
needed.
Expand Down Expand Up @@ -133,4 +133,3 @@ Now that the data has been migrated to the new `v0.7` format, you can remove the
ciphers: [
default: {Cloak.Ciphers.AES.CTR, tag: "AES.V2", key: Base.decode64("...")}
]

2 changes: 1 addition & 1 deletion guides/upgrading/0.8.x_to_0.9.x.md
Expand Up @@ -24,7 +24,7 @@ Add your vault to your supervision tree:

### Update `init/1` callback implementation

If you are fetching the keys from system vars, you should change the implementation of the `Cloak.Vault.init/1` callback to `GenServer.init/1`
If you are fetching the keys from system vars, you should change the implementation of the `init/1` callback to `GenServer.init/1`

```elixir
defmodule MyApp.Vault do
Expand Down
6 changes: 3 additions & 3 deletions lib/cloak/ciphers/aes_ctr.ex
Expand Up @@ -9,7 +9,7 @@ defmodule Cloak.Ciphers.AES.CTR do
alias Cloak.Tags.{Encoder, Decoder}

@doc """
Callback implementation for `Cloak.Cipher.encrypt`. Encrypts a value using
Callback implementation for `Cloak.Cipher`. Encrypts a value using
AES in CTR mode.
Generates a random IV for every encryption, and prepends the key tag and IV to
Expand Down Expand Up @@ -42,7 +42,7 @@ defmodule Cloak.Ciphers.AES.CTR do
end

@doc """
Callback implementation for `Cloak.Cipher.decrypt/2`. Decrypts a value
Callback implementation for `Cloak.Cipher`. Decrypts a value
encrypted with AES in CTR mode.
Uses the key tag to find the correct key for decryption, and the IV included
Expand Down Expand Up @@ -71,7 +71,7 @@ defmodule Cloak.Ciphers.AES.CTR do
end

@doc """
Callback implementation for `Cloak.Cipher.can_decrypt?2`. Determines if
Callback implementation for `Cloak.Cipher`. Determines if
a ciphertext can be decrypted with this cipher.
"""
@impl true
Expand Down
23 changes: 15 additions & 8 deletions lib/cloak/ciphers/aes_gcm.ex
Expand Up @@ -6,12 +6,13 @@ defmodule Cloak.Ciphers.AES.GCM do

@behaviour Cloak.Cipher
@aad "AES256GCM"
@default_iv_length 16

alias Cloak.Tags.Encoder
alias Cloak.Tags.Decoder

@doc """
Callback implementation for `Cloak.Cipher.encrypt/2`. Encrypts a value using
Callback implementation for `Cloak.Cipher`. Encrypts a value using
AES in GCM mode.
Generates a random IV for every encryption, and prepends the key tag, IV,
Expand All @@ -21,7 +22,7 @@ defmodule Cloak.Ciphers.AES.GCM do
+----------------------------------------------------------+----------------------+
| HEADER | BODY |
+-------------------+---------------+----------------------+----------------------+
| Key Tag (n bytes) | IV (16 bytes) | Ciphertag (16 bytes) | Ciphertext (n bytes) |
| Key Tag (n bytes) | IV (n bytes) | Ciphertag (16 bytes) | Ciphertext (n bytes) |
+-------------------+---------------+----------------------+----------------------+
| |_________________________________
| |
Expand All @@ -39,22 +40,24 @@ defmodule Cloak.Ciphers.AES.GCM do
def encrypt(plaintext, opts) do
key = Keyword.fetch!(opts, :key)
tag = Keyword.fetch!(opts, :tag)
iv = :crypto.strong_rand_bytes(16)
iv_length = Keyword.get(opts, :iv_length, @default_iv_length)
iv = :crypto.strong_rand_bytes(iv_length)

{ciphertext, ciphertag} = :crypto.block_encrypt(:aes_gcm, key, iv, {@aad, plaintext})
{:ok, Encoder.encode(tag) <> iv <> ciphertag <> ciphertext}
end

@doc """
Callback implementation for `Cloak.Cipher.decrypt/2`. Decrypts a value
Callback implementation for `Cloak.Cipher`. Decrypts a value
encrypted with AES in GCM mode.
"""
@impl true
def decrypt(ciphertext, opts) do
if can_decrypt?(ciphertext, opts) do
key = Keyword.fetch!(opts, :key)
iv_length = Keyword.get(opts, :iv_length, @default_iv_length)

%{remainder: <<iv::binary-16, ciphertag::binary-16, ciphertext::binary>>} =
%{remainder: <<iv::binary-size(iv_length), ciphertag::binary-16, ciphertext::binary>>} =
Decoder.decode(ciphertext)

{:ok, :crypto.block_decrypt(:aes_gcm, key, iv, {@aad, ciphertext, ciphertag})}
Expand All @@ -64,15 +67,19 @@ defmodule Cloak.Ciphers.AES.GCM do
end

@doc """
Callback implementation for `Cloak.Cipher.can_decrypt?/2`. Determines whether
this module can decrypt the given ciphertext.
Callback implementation for `Cloak.Cipher`. Determines whether this module
can decrypt the given ciphertext.
"""
@impl true
def can_decrypt?(ciphertext, opts) do
tag = Keyword.fetch!(opts, :tag)
iv_length = Keyword.get(opts, :iv_length, @default_iv_length)

case Decoder.decode(ciphertext) do
%{tag: ^tag, remainder: <<_iv::binary-16, _ciphertag::binary-16, _ciphertext::binary>>} ->
%{
tag: ^tag,
remainder: <<_iv::binary-size(iv_length), _ciphertag::binary-16, _ciphertext::binary>>
} ->
true

_other ->
Expand Down
8 changes: 5 additions & 3 deletions test/cloak/ciphers/aes_gcm_test.exs
Expand Up @@ -3,7 +3,7 @@ defmodule Cloak.Ciphers.AES.GCMTest do

alias Cloak.Ciphers.AES.GCM, as: Cipher

@opts [tag: "AES.GCM.V1", key: :crypto.strong_rand_bytes(32)]
@opts [tag: "AES.GCM.V1", key: :crypto.strong_rand_bytes(32), iv_length: 12]

describe ".encrypt/2" do
test "encrypts binaries" do
Expand All @@ -12,11 +12,13 @@ defmodule Cloak.Ciphers.AES.GCMTest do
end

test "returns ciphertext in the format key_tag <> iv <> ciphertag <> ciphertext" do
iv_length = @opts[:iv_length]

assert {:ok,
<<_type::binary-1, _length::binary-1, "AES.GCM.V1", iv::binary-16,
<<_type::binary-1, _length::binary-1, "AES.GCM.V1", iv::binary-size(iv_length),
ciphertag::binary-16, ciphertext::binary>>} = Cipher.encrypt("plaintext", @opts)

assert byte_size(iv) == 16
assert byte_size(iv) == iv_length
assert byte_size(ciphertag) == 16
assert String.length(ciphertext) > 0
end
Expand Down

0 comments on commit adf5574

Please sign in to comment.