Skip to content

HMAC Generator — Ruby 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 Ruby 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: Ruby 3.2 (bundled library: openssl)
# 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 Ruby's bundled `openssl` library — a default-gem C extension
# shipped with the Ruby distribution, so no gems need to be installed.
# OpenSSL::HMAC implements RFC 2104 on top of the same vetted OpenSSL digests
# the `hash` tool's Ruby port reaches through `digest`, and `hexdigest`
# returns exactly the lowercase hex the TypeScript reference produces.
#
# Behavior mirrors the TypeScript reference: UTF-8 inputs, lowercase hex
# output, SHA-256 by default, and rejection of empty secrets and unknown
# algorithms. A Ruby String is a byte sequence with an encoding, so a UTF-8
# message and secret hash as their own bytes — the same byte sequence a
# browser hands to crypto.subtle.sign. The empty-secret rejection keeps
# parity with SubtleCrypto, which refuses a zero-length key that
# OpenSSL::HMAC would otherwise accept. SHA-1 is offered for legacy
# compatibility only; it is not collision-resistant.

require 'openssl'

module HmacTool
  # Canonical algorithm names. The spellings match the TypeScript union so
  # the same string works across every port.
  SHA1 = 'SHA-1'
  SHA256 = 'SHA-256' # default algorithm
  SHA384 = 'SHA-384'
  SHA512 = 'SHA-512'

  # Dispatch table: canonical name -> OpenSSL digest name. Module-level so
  # the supported set is visible in one place and easy to extend (the same
  # shape as the Python port's _HASH_CONSTRUCTORS).
  ALGORITHMS = {
    SHA1 => 'SHA1',
    SHA256 => 'SHA256',
    SHA384 => 'SHA384',
    SHA512 => 'SHA512'
  }.freeze

  module_function

  # Computes HMAC(`message`, `secret`) under the named algorithm and returns
  # it as lowercase hex.
  #
  # A nil or empty `algorithm` selects SHA-256 — the optional-parameter
  # default the TypeScript reference declares. An empty `secret` and an
  # unknown algorithm name both raise ArgumentError (not KeyError), matching
  # the Python port's ValueError and the two arms of the Rust port's
  # HmacError enum.
  def hmac_hex(message, secret, algorithm = SHA256)
    name = algorithm.to_s.empty? ? SHA256 : algorithm

    raise ArgumentError, 'HMAC secret must not be empty' if secret.empty?

    digest_name = ALGORITHMS.fetch(name) do
      raise ArgumentError, "Unsupported HMAC algorithm: #{name}"
    end

    OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new(digest_name), secret, message)
  end
end

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 →