Skip to content

AEAD

Authenticated Encryption with Associated Data (AEAD) provides confidentiality and integrity in a single operation. Every ciphertext carries an authentication tag – if anyone tampers with the data, decryption fails immediately with an exception rather than silently producing garbage.

Assumed imports

All examples assume the following:

import dev.whyoleg.cryptography.*
import dev.whyoleg.cryptography.algorithms.*

val provider = CryptographyProvider.Default

Basic Usage

Get the algorithm, generate a key, create a cipher, and encrypt/decrypt:

val aesGcm = provider.get(AES.GCM)
val key = aesGcm.keyGenerator().generateKey()
val cipher = key.cipher()

// Encrypt
val ciphertext = cipher.encrypt(plaintext = "secret message".encodeToByteArray())

// Decrypt
val plaintext = cipher.decrypt(ciphertext = ciphertext)
println(plaintext.decodeToString()) // secret message

Each call to encrypt generates a fresh random IV and prepends it to the output: [IV | ciphertext | tag]. When you pass this to decrypt, the library splits it automatically. The cipher object is reusable – call encrypt and decrypt on it as many times as needed.

Associated Data

Associated data (AAD) is authenticated but not encrypted. Use it to bind ciphertext to a context so it cannot be replayed elsewhere:

val cipher = key.cipher()
val userId = "user-123".encodeToByteArray()

// Encrypt with associated data
val ciphertext = cipher.encrypt(
    plaintext = "secret".encodeToByteArray(),
    associatedData = userId
)

// Decrypt -- must provide the same associated data
val plaintext = cipher.decrypt(
    ciphertext = ciphertext,
    associatedData = userId
)

If you provide different associated data at decryption – or omit it entirely – the authentication tag will not match and decryption throws an exception. The associated data itself is never included in the ciphertext output; both sides must know it independently.

Custom IV

By default, a random IV is generated and prepended to the ciphertext. If your protocol requires a specific IV handling, use encryptWithIv and decryptWithIv:

val cipher = key.cipher()
val iv = ByteArray(12) // 12 bytes for AES-GCM

val ciphertext = cipher.encryptWithIv(
    iv = iv,
    plaintext = "secret".encodeToByteArray()
)

// With custom IV, the output does NOT contain the IV -- only [ciphertext | tag]
val plaintext = cipher.decryptWithIv(
    iv = iv,
    ciphertext = ciphertext
)

Warning

Reusing an IV with the same key completely breaks AES-GCM security. Only use custom IVs when you have a reliable mechanism to guarantee uniqueness.

Streaming

For large data that does not fit in memory, use the kotlinx-io streaming API. The cipher provides an ability to transform RawSource via encryptingSource and decryptingSource as well as RawSink with encryptingSink and decryptingSink:

val cipher = key.cipher()
val aad = "context".encodeToByteArray()

// Pull-based: wrap a source
val encryptedSource: RawSource = cipher.encryptingSource(plaintextSource, associatedData = aad)
val decryptedSource: RawSource = cipher.decryptingSource(ciphertextSource, associatedData = aad)

// Push-based: wrap a sink
val encryptingSink: RawSink = cipher.encryptingSink(destinationSink, associatedData = aad)
val decryptingSink: RawSink = cipher.decryptingSink(plaintextSink, associatedData = aad)

Custom IV variants are also available: encryptingSourceWithIv, encryptingSinkWithIv, decryptingSourceWithIv, decryptingSinkWithIv.

Supported Algorithms

Algorithm JDK WebCrypto Apple CryptoKit OpenSSL3
AES-GCM ⭐ ✅ ✅ 1 ❌ ✅ 2 ✅
AES-CCM ✅ 3 ❌ ❌ ❌ ✅
ChaCha20-Poly1305 ⭐ ✅ 4 ❌ ❌ ✅ ✅

  1. 192-bit keys may not be supported in some browsers 

  2. 128-bit (default) tag only 

  3. Requires BouncyCastle 

  4. Requires JDK 11+; use BouncyCastle on older JDK or Android