Current section
Files
Jump to
Current section
Files
lib/cloak/ciphers/aes_ctr.ex
defmodule Cloak.Ciphers.AES.CTR do
@moduledoc """
A `Cloak.Cipher` which encrypts values with the AES cipher in CTR (stream) mode.
Internally relies on Erlang's `:crypto.stream_encrypt/2`.
"""
@behaviour Cloak.Cipher
alias Cloak.Tags.{Encoder, Decoder}
@doc """
Callback implementation for `Cloak.Cipher.encrypt`. Encrypts a value using
AES in CTR mode.
Generates a random IV for every encryption, and prepends the key tag and IV to
the beginning of the ciphertext. The format can be diagrammed like this:
+-----------------------------------+----------------------+
| HEADER | BODY |
+-------------------+---------------+----------------------+
| Key Tag (n bytes) | IV (16 bytes) | Ciphertext (n bytes) |
+-------------------+---------------+----------------------+
| |__________________________________
| |
+---------------+-----------------+-------------------+
| Type (1 byte) | Length (1 byte) | Key Tag (n bytes) |
+---------------+-----------------+-------------------+
The `Key Tag` component of the header breaks down into a `Type`, `Length`,
and `Value` triplet for easy decoding.
"""
@impl true
def encrypt(plaintext, opts) when is_binary(plaintext) do
key = Keyword.fetch!(opts, :key)
tag = Keyword.fetch!(opts, :tag)
iv = :crypto.strong_rand_bytes(16)
state = :crypto.stream_init(:aes_ctr, key, iv)
{_state, ciphertext} = :crypto.stream_encrypt(state, to_string(plaintext))
{:ok, Encoder.encode(tag) <> iv <> ciphertext}
end
@doc """
Callback implementation for `Cloak.Cipher.decrypt/2`. Decrypts a value
encrypted with AES in CTR mode.
Uses the key tag to find the correct key for decryption, and the IV included
in the header to decrypt the body of the ciphertext.
### Parameters
- `ciphertext` - Binary ciphertext generated by `encrypt/2`.
### Examples
iex> encrypt("Hello") |> decrypt
"Hello"
"""
@impl true
def decrypt(ciphertext, opts) when is_binary(ciphertext) do
if can_decrypt?(ciphertext, opts) do
key = Keyword.fetch!(opts, :key)
%{remainder: <<iv::binary-16, ciphertext::binary>>} = Decoder.decode(ciphertext)
state = :crypto.stream_init(:aes_ctr, key, iv)
{_state, plaintext} = :crypto.stream_decrypt(state, ciphertext)
{:ok, plaintext}
else
:error
end
end
@doc """
Callback implementation for `Cloak.Cipher.can_decrypt?2`. Determines if
a ciphertext can be decrypted with this cipher.
"""
@impl true
def can_decrypt?(ciphertext, opts) when is_binary(ciphertext) do
tag = Keyword.fetch!(opts, :tag)
case Decoder.decode(ciphertext) do
%{tag: ^tag, remainder: <<_iv::binary-16, _ciphertext::binary>>} ->
true
_other ->
false
end
end
end