Hash Type Identifier — Ruby source
Identify the likely hash algorithm of a hash string by its length and character set - MD5, SHA-1/2/3, BLAKE, CRC32, NTLM, bcrypt, Argon2 and more.
This is the Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# frozen_string_literal: true
# Hash-type identifier — Ruby port.
#
# Language: Ruby (3.2+, standard library only)
# Source: CosmoDev polyglot showcase port of the `hash-type-identifier`
# tool, ported from src/lib/hashIdentify.ts (the canonical
# TypeScript implementation).
# License: display source — part of CosmoDev's polyglot tool pages
# (dev.cosmolabs.org).
#
# Pure string classification: inspect a candidate hash's charset and length
# to suggest likely algorithms. No hashing happens here — this is pattern
# recognition over an already-computed digest. Deterministic; never raises.
module HashTypeIdentifier
# Hex candidates keyed by hex-string length. Each hex char encodes 4 bits,
# so a 64-char digest implies a 256-bit algorithm such as SHA-256.
HEX_BY_LENGTH = {
8 => %w[CRC32 Adler-32],
16 => ['MySQL 3.x', 'CRC64'],
32 => %w[MD5 MD4 NTLM LM MD2 RIPEMD-128 HAVAL-128],
40 => ['SHA-1', 'RIPEMD-160', 'HAVAL-160', 'MySQL 5.x (SHA1(SHA1))', 'Tiger-160'],
56 => %w[SHA-224 SHA3-224 BLAKE2s-224 HAVAL-224],
64 => %w[SHA-256 SHA3-256 BLAKE2s-256 RIPEMD-256 Skein-256],
96 => %w[SHA-384 SHA3-384 BLAKE2b-384],
128 => %w[SHA-512 SHA3-512 BLAKE2b-512 Whirlpool Skein-512],
}.freeze
# Base64 candidates keyed by encoded-string length (16-byte MD5 digest ->
# 24 base64 chars including padding, etc.).
BASE64_BY_LENGTH = {
24 => ['MD5 (base64)'],
28 => ['SHA-1 (base64)'],
44 => ['SHA-256 (base64)'],
88 => ['SHA-512 (base64)'],
}.freeze
# A candidate hash algorithm and its nominal bit length.
HashMatch = Struct.new(:name, :bit_length, keyword_init: true)
# The full identification result for an input string.
HashInfo = Struct.new(:input, :cleaned, :length, :charset, :candidates, keyword_init: true)
# Detection patterns. bcrypt and argon2 use their modular-crypt `$...$`
# format, so they are matched by prefix (the trailing payload is variable);
# the patterns carry no end anchor. hex and base64 match the whole string,
# and hex is checked first because any hex digest is also a legal base64
# character set.
RE_BCRYPT = /\A\$2[abxy]?\$/
RE_ARGON2 = /\A\$argon2(id|i|d)?\$/
RE_HEX = /\A[0-9a-fA-F]+\z/
RE_BASE64 = /\A[A-Za-z0-9+\/]+={0,2}\z/
module_function
# Classify the charset of a candidate hash string.
def detect_charset(s)
return 'bcrypt' if RE_BCRYPT.match?(s)
return 'argon2' if RE_ARGON2.match?(s)
return 'hex' if RE_HEX.match?(s)
return 'base64' if RE_BASE64.match?(s)
'unknown'
end
# Identify candidate hash types for an input string.
#
# Always returns a HashInfo; never raises. An empty, unrecognised, or
# wrong-length input simply yields an empty `candidates` list — the caller
# decides whether "no candidates" means "not a hash".
def identify_hash(input)
original = input.to_s
cleaned = original.strip
charset = detect_charset(cleaned)
length = cleaned.length
candidates = []
case charset
when 'bcrypt'
# bcrypt's modular-crypt token encodes a 184-bit effective hash.
candidates << HashMatch.new(name: 'bcrypt', bit_length: 184)
when 'argon2'
# Argon2 output length is parameter-driven, so no fixed bit length applies.
candidates << HashMatch.new(name: 'Argon2', bit_length: 0)
when 'hex'
# length*4 converts hex-char count to a bit width (4 bits per nibble).
HEX_BY_LENGTH.fetch(length, []).each do |name|
candidates << HashMatch.new(name: name, bit_length: length * 4)
end
when 'base64'
# Each base64 char carries 6 bits; round to the nearest byte boundary
# so the reported length lines up with the underlying digest width. All
# table lengths divide evenly, so rounding is exact here.
bit_length = ((length * 6) / 8.0).round * 8
BASE64_BY_LENGTH.fetch(length, []).each do |name|
candidates << HashMatch.new(name: name, bit_length: bit_length)
end
end
HashInfo.new(input: original, cleaned: cleaned, length: length,
charset: charset, candidates: candidates)
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 →