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:
verifySignaturethrows an exception on failure.tryVerifySignaturereturnstrueorfalse.
// 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 | |||||
| HMAC |
-
Requires BouncyCastle ↩