HMAC Generator — Swift 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 Swift 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: Swift 5.9 (Apple CryptoKit / swift-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.
//
// The TypeScript reference delegates to `crypto.subtle.sign` with an HMAC
// key, and the Rust port uses the RustCrypto `hmac`/`sha1`/`sha2` crates.
// The Swift standard library ships no crypto, so the idiomatic path is
// Apple's CryptoKit — and on non-Apple platforms the open-source
// swift-crypto package, which exposes the same HMAC/SHA types under
// `import Crypto` (the ecosystem equivalent named where Rust names its
// crates). The canImport switch below picks the right module either way.
//
// Behavior mirrors the TypeScript reference: UTF-8 inputs, lowercase hex
// output, SHA-256 by default, and rejection of empty secrets and unknown
// algorithms (HmacError, the Swift standing of the Rust port's HmacError
// enum). The empty-secret case matters for parity: HMAC's authenticationCode
// would accept a zero-length SymmetricKey, but SubtleCrypto refuses one.
// SHA-1 is offered for legacy compatibility only; it is not
// collision-resistant.
import Foundation
#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto // swift-crypto on Linux
#endif
/// Canonical algorithm names. The spellings match the TypeScript union so the
/// same string works across every port. The case order matches the order the
/// React island renders.
public enum HmacAlgorithm: String, CaseIterable, Sendable {
case sha1 = "SHA-1"
case sha256 = "SHA-256"
case sha384 = "SHA-384"
case sha512 = "SHA-512"
/// The algorithm selected when a caller passes an empty name — the
/// optional-parameter default the TypeScript reference declares.
public static let `default`: HmacAlgorithm = .sha256
}
/// Errors thrown by `hmacHex`. A concrete enum (not a string) so callers can
/// switch on the kind — the same shape as the Rust port's HmacError.
public enum HmacError: Error, Equatable, Sendable {
/// The algorithm string was not one of the supported names.
case unknownAlgorithm(String)
/// An empty secret was supplied; the TypeScript reference rejects this too.
case emptySecret
}
/// Lowercase hexadecimal encoding of a byte sequence — Foundation-free, the
/// Swift shape of the Rust port's `to_hex`.
private func toHex<S: Sequence>(_ bytes: S) -> String where S.Element == UInt8 {
let digits = Array("0123456789abcdef".unicodeScalars)
var out = String.UnicodeScalarView()
for byte in bytes {
out.append(digits[Int(byte >> 4)])
out.append(digits[Int(byte & 0x0f)])
}
return String(out)
}
/// Computes HMAC(`message`, `secret`) under the named algorithm and returns
/// it as lowercase hex.
///
/// Both inputs are UTF-8 encoded before hashing (String.utf8), so the result
/// is correct for arbitrary Unicode (emoji, accents, CJK). Passing an empty
/// `algorithm` selects SHA-256; an empty `secret` throws
/// `HmacError.emptySecret`, and an unknown name throws
/// `HmacError.unknownAlgorithm`.
public func hmacHex(_ message: String, secret: String, algorithm: String = "") throws -> String {
guard !secret.isEmpty else {
throw HmacError.emptySecret
}
// A String's utf8 view is the exact byte sequence crypto.subtle.sign
// receives from TextEncoder in the TypeScript reference.
let key = SymmetricKey(data: Data(secret.utf8))
let data = Data(message.utf8)
switch algorithm {
case HmacAlgorithm.sha1.rawValue:
// CryptoKit keeps SHA-1 behind the Insecure namespace (as does
// swift-crypto) to signal its broken collision resistance.
return toHex(HMAC<Insecure.SHA1>.authenticationCode(for: data, using: key))
case HmacAlgorithm.sha256.rawValue, "":
return toHex(HMAC<SHA256>.authenticationCode(for: data, using: key))
case HmacAlgorithm.sha384.rawValue:
return toHex(HMAC<SHA384>.authenticationCode(for: data, using: key))
case HmacAlgorithm.sha512.rawValue:
return toHex(HMAC<SHA512>.authenticationCode(for: data, using: key))
default:
throw HmacError.unknownAlgorithm(algorithm)
}
}
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 →