Module: RosettAi::Mcp::KeyHasher

Defined in:
lib/rosett_ai/mcp/key_hasher.rb

Overview

Cryptographic key hashing with per-key random salt.

Uses SHA-256 with 16-byte random salt. Verification uses constant-time comparison via Rack::Utils.secure_compare.

Author:

  • hugo

  • claude

Constant Summary collapse

SALT_BYTES =

Returns Number of random bytes used for key salting.

Returns:

  • (Integer)

    Number of random bytes used for key salting.

16
KEY_PREFIX =

Returns Prefix for hashed API key identifiers.

Returns:

  • (String)

    Prefix for hashed API key identifiers.

'nncc_'

Class Method Summary collapse

Class Method Details

.generate_keyString

Generate a new random API key with the Rosett-AI_ prefix.

Returns:

  • (String)

    a new API key



52
53
54
# File 'lib/rosett_ai/mcp/key_hasher.rb', line 52

def generate_key
  "#{KEY_PREFIX}#{SecureRandom.hex(24)}"
end

.hash_key(plaintext) ⇒ String

Hash a plaintext API key with a random salt.

Parameters:

  • plaintext (String)

    the plaintext key

Returns:

  • (String)

    salted hash in format "salt$digest"



30
31
32
33
34
# File 'lib/rosett_ai/mcp/key_hasher.rb', line 30

def hash_key(plaintext)
  salt = SecureRandom.hex(SALT_BYTES)
  digest = OpenSSL::Digest::SHA256.hexdigest("#{salt}#{plaintext}")
  "#{salt}$#{digest}"
end

.secure_compare(a_str, b_str) ⇒ Boolean

Constant-time string comparison to prevent timing attacks.

Parameters:

  • a_str (String)

    first string

  • b_str (String)

    second string

Returns:

  • (Boolean)

    true if strings match



61
62
63
64
65
# File 'lib/rosett_ai/mcp/key_hasher.rb', line 61

def secure_compare(a_str, b_str)
  return false unless a_str.bytesize == b_str.bytesize

  OpenSSL.fixed_length_secure_compare(a_str, b_str)
end

.verify_key(plaintext, stored_hash) ⇒ Boolean

Verify a plaintext key against a stored hash.

Parameters:

  • plaintext (String)

    the plaintext key to verify

  • stored_hash (String)

    the stored "salt$digest" hash

Returns:

  • (Boolean)

    true if the key matches



41
42
43
44
45
46
47
# File 'lib/rosett_ai/mcp/key_hasher.rb', line 41

def verify_key(plaintext, stored_hash)
  salt, expected_digest = stored_hash.split('$', 2)
  return false unless salt && expected_digest

  actual_digest = OpenSSL::Digest::SHA256.hexdigest("#{salt}#{plaintext}")
  secure_compare(actual_digest, expected_digest)
end