Skip to content

MAC

A Message Authentication Code (MAC) produces a short, fixed-size tag from a message and a secret key. The tag proves both integrity (the message was not modified) and authenticity (it was created by someone who holds the key). Unlike a plain hash, only a party with the correct key can generate or verify the tag.

Assumed imports

All code examples on this page assume the following imports and provider setup:

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

val provider = CryptographyProvider.Default

Basic Usage

Generate an HMAC key, sign a message, and verify the tag:

val hmac = provider.get(HMAC)
val key = hmac.keyGenerator(SHA256).generateKey()

// Generate a MAC tag (called "signature" in the API)
val signature = key.signatureGenerator().generateSignature(
    "Hello, World!".encodeToByteArray()
)

// Verify -- throws if the signature is invalid
key.signatureVerifier().verifySignature(
    "Hello, World!".encodeToByteArray(),
    signature
)

There are two ways to verify the signature:

// Option 1: throws on failure
key.signatureVerifier().verifySignature(data, signature)

// Option 2: Boolean result
val isValid = key.signatureVerifier().tryVerifySignature(data, signature)

Both signatureGenerator and signatureVerifier return reusable objects – create them once and call generateSignature or verifySignature as many times as needed.

For larger data, the overload that accepts a RawSource from kotlinx-io could be used instead:

val source: RawSource = ... // file, network stream, etc.
val signature = key.signatureGenerator().generateSignature(source)
key.signatureVerifier().verifySignature(source, signature)

Pass-Through

Use updatingSource or updatingSink to compute a MAC as data flows through a kotlinx-io pipeline:

val signFunction = key.signatureGenerator().createSignFunction()

// Wrap a source -- data passes through AND is fed into the MAC
val macSource: RawSource = signFunction.updatingSource(dataSource)

// Read all data (it goes to both the consumer and the MAC)
val data = macSource.readByteArray()

// After reading, the signature is ready
val signature = signFunction.signToByteArray()

Both SignFunction and VerifyFunction implement AutoCloseable and support reset for a reuse. After finalization, call close, or wrap it in a use block:

val signFunction = key.signatureGenerator().createSignFunction().use { hf ->
    // ... feed data to function
    hf.signToByteArray()
}

For the most control, use update directly to feed data in arbitrary chunks:

val signFunction = key.signatureGenerator().createSignFunction()

signFunction.update("chunk1".encodeToByteArray())
signFunction.update("chunk2".encodeToByteArray())

val signature = signFunction.signToByteArray()

Incremental verification works the same way:

val verifyFunction = key.signatureVerifier().createVerifyFunction()

verifyFunction.update("chunk1".encodeToByteArray())
verifyFunction.update("chunk2".encodeToByteArray())

verifyFunction.verify(signature) // throws on failure

Supported Algorithms

Algorithm JDK WebCrypto Apple CryptoKit OpenSSL3
AES-CMAC ✅ 1 ❌ ❌ ❌ ✅
HMAC ✅ ✅ ✅ ✅ ✅

  1. Requires BouncyCastle