HMAC Generator — Kotlin source
Generate a keyed-hash HMAC (SHA-1/256/384/512) for a message and secret. Runs entirely in your browser via Web Crypto, with a shareable link to your exact input.
This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// hmac-generator — RFC 2104 keyed-hash HMAC of a UTF-8 message, hex output.
//
// Language: Kotlin 1.9 (JVM, standard library + javax.crypto)
// Source: CosmoDev polyglot showcase port of the `hmac-generator` tool,
// ported from src/lib/hmac.ts (the canonical TypeScript lib).
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Kotlin/JVM has no crypto in kotlin-stdlib, so the idiomatic path is the
// JDK's javax.crypto.Mac — the same vetted provider set the Java port of
// this tool uses. All four "HmacSHA*" names are standard JCA algorithm
// strings; no external dependencies are needed.
//
// Behavior mirrors the TypeScript reference: UTF-8 inputs, lowercase hex
// output, SHA-256 by default, and rejection of empty secrets and unknown
// algorithms. An empty secret and an unknown name both throw
// IllegalArgumentException — the Kotlin standing of the Python port's
// ValueError and the two arms of the Rust port's HmacError enum (Mac would
// accept a zero-length key; SubtleCrypto refuses one, and the ports keep
// parity). SHA-1 is offered for legacy compatibility only; it is not
// collision-resistant.
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
/** Pure logic of the CosmoDev `hmac-generator` tool. */
object HmacTool {
/** Canonical algorithm names. The spellings match the TypeScript union so
* the same string works across every port. */
const val SHA1: String = "SHA-1"
const val SHA256: String = "SHA-256" // default algorithm
const val SHA384: String = "SHA-384"
const val SHA512: String = "SHA-512"
/** Every supported algorithm, in the order the React island renders them. */
val ALGORITHMS: List<String> = listOf(SHA1, SHA256, SHA384, SHA512)
/** Dispatch table: canonical name -> JCA Mac algorithm string (the same
* shape as the Python port's _HASH_CONSTRUCTORS). buildMap keeps
* insertion order through LinkedHashMap, exactly like the Java port. */
private val JCA_NAMES: Map<String, String> = buildMap {
put(SHA1, "HmacSHA1")
put(SHA256, "HmacSHA256")
put(SHA384, "HmacSHA384")
put(SHA512, "HmacSHA512")
}
/**
* Computes HMAC([message], [secret]) under the named algorithm and
* returns it as lowercase hex.
*
* Both inputs are UTF-8 encoded before hashing, so the result is correct
* for arbitrary Unicode (emoji, accents, CJK). A null or empty
* [algorithm] selects SHA-256 — the default parameter the TypeScript
* reference declares. An empty [secret] or an unknown name throws
* [IllegalArgumentException].
*/
fun hmacHex(message: String, secret: String, algorithm: String = SHA256): String {
val name = algorithm.takeIf { it.isNotEmpty() } ?: SHA256
// Mac would accept a zero-length key; SubtleCrypto refuses one.
require(secret.isNotEmpty()) { "HMAC secret must not be empty" }
val jcaName = JCA_NAMES[name]
?: throw IllegalArgumentException("Unsupported HMAC algorithm: $name")
// A fresh instance per call: Mac is stateful. SecretKeySpec wraps the
// UTF-8 key bytes — the same byte sequence a browser hands to
// crypto.subtle.sign with an HmacImportParams key.
return try {
val mac = Mac.getInstance(jcaName)
mac.init(SecretKeySpec(secret.toByteArray(Charsets.UTF_8), jcaName))
val tag = mac.doFinal(message.toByteArray(Charsets.UTF_8))
// Two lowercase hex digits per byte — %02x formats a Byte as its
// unsigned two-digit hex value.
tag.joinToString(separator = "") { "%02x".format(it) }
} catch (e: NoSuchAlgorithmException) {
// Unreachable on a conforming JDK: the four HmacSHA* names are
// mandatory JCA algorithms.
throw IllegalArgumentException("Unsupported HMAC algorithm: $name", e)
} catch (e: InvalidKeyException) {
// Unreachable: HMAC accepts keys of any length per RFC 2104, and
// the empty-secret guard above already rejected the one case
// SecretKeySpec would refuse.
throw IllegalArgumentException("Invalid HMAC key", e)
}
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →