kryptos/block

Block cipher implementations and modes of operation.

AES block ciphers and modes of operation (ECB, CBC, CTR).

IMPORTANT SECURITY WARNING: ECB, CBC, and CTR modes do NOT provide authentication. An attacker can modify ciphertext without detection. For most applications, you should use authenticated encryption modes like AES-GCM or ChaCha20-Poly1305 from the kryptos/aead module instead.

Use these modes only when:

Modes Overview

Example

import kryptos/block
import kryptos/crypto

// CBC encryption with random IV
let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32))
let assert Ok(ctx) = block.cbc(cipher, iv: crypto.random_bytes(16))
let assert Ok(ciphertext) = block.encrypt(ctx, <<"secret":utf8>>)
let assert Ok(decrypted) = block.decrypt(ctx, ciphertext)
// decrypted == <<"secret":utf8>>

Types

A block cipher with its associated key material.

pub opaque type BlockCipher

Context for block cipher modes of operation.

Use the provided constructor functions to create contexts:

  • ecb() for ECB mode
  • cbc() for CBC mode
  • ctr() for CTR mode
pub opaque type CipherContext

Values

pub fn aes_128(key: BitArray) -> Result(BlockCipher, Nil)

Creates a new AES-128 block cipher with the given key.

The key must be exactly 16 bytes.

pub fn aes_192(key: BitArray) -> Result(BlockCipher, Nil)

Creates a new AES-192 block cipher with the given key.

The key must be exactly 24 bytes.

pub fn aes_256(key: BitArray) -> Result(BlockCipher, Nil)

Creates a new AES-256 block cipher with the given key.

The key must be exactly 32 bytes.

pub fn block_size(cipher: BlockCipher) -> Int

Returns the block size in bytes for a block cipher.

pub fn cbc(
  cipher: BlockCipher,
  iv iv: BitArray,
) -> Result(CipherContext, Nil)

Creates a CBC mode context with the given cipher and IV.

The IV must be exactly 16 bytes, random, and unique per encryption.

pub fn ctr(
  cipher: BlockCipher,
  nonce nonce: BitArray,
) -> Result(CipherContext, Nil)

Creates a CTR mode context with the given cipher and nonce.

SECURITY WARNING: Nonce reuse is catastrophic in CTR mode. NEVER reuse a nonce with the same key.

The nonce must be exactly 16 bytes.

Example

import kryptos/block
import kryptos/crypto

let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32))
let assert Ok(ctx) = block.ctr(cipher, nonce: crypto.random_bytes(16))
let assert Ok(ciphertext) = block.encrypt(ctx, <<"secret":utf8>>)
let assert Ok(plaintext) = block.decrypt(ctx, ciphertext)
pub fn decrypt(
  ctx: CipherContext,
  ciphertext: BitArray,
) -> Result(BitArray, Nil)

Decrypts ciphertext using the cipher mode.

Notes

  • ECB: No IV required
  • CBC: Automatically removes PKCS7 padding; returns error if padding is invalid
  • CTR: No padding; ciphertext size equals plaintext size
pub fn ecb(cipher: BlockCipher) -> CipherContext

Creates an ECB mode context for the given cipher.

SECURITY WARNING: ECB mode is insecure for most use cases. Identical plaintext blocks produce identical ciphertext blocks, revealing patterns in the data.

pub fn encrypt(
  ctx: CipherContext,
  plaintext: BitArray,
) -> Result(BitArray, Nil)

Encrypts plaintext using the cipher mode.

Notes

  • ECB: No IV required
  • CBC: Automatically applies PKCS7 padding; ciphertext may be larger than plaintext
  • CTR: No padding needed; ciphertext is same size as plaintext
pub fn key_size(cipher: BlockCipher) -> Int

Returns the key size in bits for a block cipher.

pub fn unwrap(
  cipher: BlockCipher,
  ciphertext: BitArray,
) -> Result(BitArray, Nil)

Unwraps key material using AES Key Wrap (RFC 3394).

The ciphertext must be a multiple of 8 bytes, minimum 24 bytes.

Example

import kryptos/block

let assert Ok(kek) = block.aes_256(kek_bytes)
let assert Ok(unwrapped) = block.unwrap(kek, wrapped_key)
pub fn wrap(
  cipher: BlockCipher,
  plaintext: BitArray,
) -> Result(BitArray, Nil)

Wraps key material using AES Key Wrap (RFC 3394).

Key wrapping is used to protect cryptographic keys when they need to be transported or stored. Unlike general encryption, key wrapping:

  • Does not require an IV (uses a default IV internally)
  • Provides integrity protection
  • Output is always 8 bytes larger than input

The plaintext must be a multiple of 8 bytes, minimum 16 bytes.

Example

import kryptos/block
import kryptos/crypto

let assert Ok(kek) = block.aes_256(crypto.random_bytes(32))
let key_to_wrap = crypto.random_bytes(32)
let assert Ok(wrapped) = block.wrap(kek, key_to_wrap)
Search Document