HMAC Generator — Java 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 Java 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: Java 17 (standard library only)
// 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.
//
// Keyed HMAC via javax.crypto.Mac — the JDK's vetted provider set (typically
// backed by OpenSSL through the SunJCE native patches). All four "HmacSHA*"
// names are standard JCA algorithm strings, so no external libraries are
// needed. Hex encoding uses Character.forDigit — no formatter and no
// third-party Hex class.
//
// 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 Java standing of the ValueError the Python
// port raises and the two arms of the Rust port's HmacError enum. The empty
// secret matters for parity: Mac would accept a zero-length key, but the
// TypeScript reference (SubtleCrypto) refuses one. SHA-1 is offered for
// legacy compatibility only; it is not collision-resistant.
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/** Pure logic of the CosmoDev {@code hmac-generator} tool. */
public final class HmacTool {
/** Canonical algorithm names. The spellings match the TypeScript union so
* the same string works across every port. */
public static final String SHA1 = "SHA-1";
public static final String SHA256 = "SHA-256"; // default algorithm
public static final String SHA384 = "SHA-384";
public static final String SHA512 = "SHA-512";
/** The algorithm selected when a caller passes null or "" — the
* optional-parameter default the TypeScript reference declares. */
public static final String DEFAULT_ALGORITHM = SHA256;
/** Every supported algorithm, in the order the React island renders them. */
public static final List<String> ALGORITHMS = List.of(SHA1, SHA256, SHA384, SHA512);
/** Dispatch table: canonical name -> JCA Mac algorithm string. Module-level
* so the supported set is visible in one place (the same shape as the
* Python port's _HASH_CONSTRUCTORS). */
private static final Map<String, String> JCA_NAMES = new LinkedHashMap<>();
static {
JCA_NAMES.put(SHA1, "HmacSHA1");
JCA_NAMES.put(SHA256, "HmacSHA256");
JCA_NAMES.put(SHA384, "HmacSHA384");
JCA_NAMES.put(SHA512, "HmacSHA512");
}
private HmacTool() {
// Static utility — no instances.
}
/**
* Computes HMAC({@code message}, {@code secret}) under the named
* algorithm and returns it as lowercase hex.
*
* <p>Both inputs are UTF-8 encoded before hashing, so the result is
* correct for arbitrary Unicode (emoji, accents, CJK). A null or empty
* {@code algorithm} selects SHA-256. An empty {@code secret} or an
* unknown algorithm name throws {@link IllegalArgumentException}.
*/
public static String hmacHex(String message, String secret, String algorithm) {
String name = (algorithm == null || algorithm.isEmpty()) ? DEFAULT_ALGORITHM : algorithm;
if (secret == null || secret.isEmpty()) {
throw new IllegalArgumentException("HMAC secret must not be empty");
}
String jcaName = JCA_NAMES.get(name);
if (jcaName == null) {
throw new IllegalArgumentException("Unsupported HMAC algorithm: " + name);
}
// A fresh instance per call: Mac is stateful, and reusing one across
// tags would need reset() bookkeeping. SecretKeySpec wraps the UTF-8
// key bytes — the same byte sequence a browser hands to
// crypto.subtle.sign with an HmacImportParams key.
try {
Mac mac = Mac.getInstance(jcaName);
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), jcaName));
byte[] tag = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
// Two hex digits per byte, lowercase — Character.forDigit already
// yields lowercase for bases 16 and below.
StringBuilder hex = new StringBuilder(tag.length * 2);
for (byte b : tag) {
hex.append(Character.forDigit((b >> 4) & 0xf, 16));
hex.append(Character.forDigit(b & 0xf, 16));
}
return hex.toString();
} catch (NoSuchAlgorithmException e) {
// Unreachable on a conforming JDK: the four HmacSHA* names are
// mandatory JCA algorithms. Translated for the same reason as
// above — a bad name is a caller bug, not an environment failure.
throw new IllegalArgumentException("Unsupported HMAC algorithm: " + name, e);
} catch (InvalidKeyException e) {
// 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 new 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 →