SpeckEx.AEAD (SpeckEx v0.1.0)

View Source

Authenticated Encryption with Associated Data (AEAD) using Speck-Poly1305.

This module provides authenticated encryption using Speck in CTR mode combined with Poly1305 MAC for authentication. This construction provides both confidentiality and authenticity, protecting against tampering and forgery.

Here be dragons

This is a "no guardrails" implementation module, where nonce generation is your own responsibility. Use the main SpeckEx module unless you know what you are doing. Be sure to read the SpeckEx security guidelines.

Supported Variants

Only Speck variants with 32, 64 and 128-bit block sizes are supported because of limitations of the backing Rust "ctr" crate:

  • :speck32_64 - 32-bit block, 64-bit key (4-byte block, 8-byte key)
  • :speck64_96 - 64-bit block, 96-bit key (8-byte block, 12-byte key)
  • :speck64_128 - 64-bit block, 128-bit key (8-byte block, 16-byte key)
  • :speck128_128 - 128-bit block, 128-bit key (16-byte block, 16-byte key)
  • :speck128_192 - 128-bit block, 192-bit key (16-byte block, 24-byte key)
  • :speck128_256 - 128-bit block, 256-bit key (16-byte block, 32-byte key, default)

Usage

# Generate a random key (32 bytes for default speck128_256)
iex> key = :crypto.strong_rand_bytes(32)
iex> nonce = :crypto.strong_rand_bytes(16)
iex> aad = "user_id:12345"
iex> {ciphertext, tag} = SpeckEx.AEAD.encrypt("Secret message", key, nonce, aad)
iex> SpeckEx.AEAD.decrypt(ciphertext, tag, key, nonce, aad)
{:ok, "Secret message"}

Associated Data (AAD)

The associated data parameter allows you to authenticate additional data that is not encrypted. This is useful for protocol headers, metadata, or any data that must be authenticated but not kept confidential.

  • AAD can be empty ("")
  • AAD is not included in the ciphertext
  • Modifying AAD will cause authentication to fail

Why Speck-Poly1305

Most authenticated encryption modes are only defined for 128 bits ciphers, which makes sense because they tend to create a verification tag of the block size of the underlying cipher, and a 64-bits MAC is too short to be secure. By using Poly1305, the MAC (or tag) is always 128 bits regardless of the block size of the cipher used to generate the Poly1305 key. This is somewhat sanctioned, at least, by RFC8439:

There is nothing special about AES here. One can replace AES with an arbitrary keyed function from an arbitrary set of nonces to 16-byte strings.

Summary

Types

AEAD mode supports Speck variants with 32, 64 and 128-bit block sizes.

Functions

Verifies the authentication tag and decrypts the ciphertext.

Encrypts plaintext and computes an authentication tag.

Types

variant()

@type variant() :: SpeckEx.CTR.variant()

AEAD mode supports Speck variants with 32, 64 and 128-bit block sizes.

Functions

decrypt(ciphertext, tag, key, nonce, aad, variant \\ :speck128_256)

@spec decrypt(binary(), binary(), binary(), binary(), binary(), variant()) ::
  {:ok, binary()} | {:error, :authentication_failed}

Verifies the authentication tag and decrypts the ciphertext.

Returns {:ok, plaintext} if authentication succeeds, or {:error, :authentication_failed} if the tag is invalid.

Parameters

  • ciphertext - The encrypted data (binary, any length)
  • tag - The 16-byte Poly1305 authentication tag
  • key - The encryption key (size depends on variant)
  • nonce - The nonce (size depends on variant, must match encryption)
  • aad - Associated authenticated data (must match encryption)
  • variant - The Speck variant to use (default: :speck128_256)

Examples

# Successful decryption
iex> key = :crypto.strong_rand_bytes(32)
iex> nonce = :crypto.strong_rand_bytes(16)
iex> {ciphertext, tag} = SpeckEx.AEAD.encrypt("Hello", key, nonce, "aad")
iex> SpeckEx.AEAD.decrypt(ciphertext, tag, key, nonce, "aad")
{:ok, "Hello"}

# Failed authentication
iex> key = :crypto.strong_rand_bytes(32)
iex> nonce = :crypto.strong_rand_bytes(16)
iex> {ciphertext, tag} = SpeckEx.AEAD.encrypt("Hello", key, nonce, "aad")
iex> tampered = <<0>> <> binary_part(ciphertext, 1, byte_size(ciphertext) - 1)
iex> SpeckEx.AEAD.decrypt(tampered, tag, key, nonce, "aad")
{:error, :authentication_failed}

encrypt(plaintext, key, nonce, aad, variant \\ :speck128_256)

@spec encrypt(binary(), binary(), binary(), binary(), variant()) ::
  {binary(), binary()}

Encrypts plaintext and computes an authentication tag.

Returns a tuple {ciphertext, tag} where:

  • ciphertext is the encrypted data (same length as plaintext)
  • tag is a 16-byte Poly1305 authentication tag

Parameters

  • plaintext - The data to encrypt (binary, any length)
  • key - The encryption key (size depends on variant)
  • nonce - The nonce (size depends on variant, MUST be unique per key)
  • aad - Associated authenticated data (binary, any length, can be empty)
  • variant - The Speck variant to use (default: :speck128_256)

Examples

iex> key = :crypto.strong_rand_bytes(32)
iex> nonce = :crypto.strong_rand_bytes(16)
iex> {ciphertext, tag} = SpeckEx.AEAD.encrypt("Hello", key, nonce, "metadata")
iex> is_binary(ciphertext) and byte_size(tag) == 16
true