113 lines
2.7 KiB
Ruby
113 lines
2.7 KiB
Ruby
|
# Copyright (C) 2001 Daiki Ueno <ueno@unixuser.org>
|
||
|
# This library is distributed under the terms of the Ruby license.
|
||
|
|
||
|
# This module provides common interface to HMAC engines.
|
||
|
# HMAC standard is documented in RFC 2104:
|
||
|
#
|
||
|
# H. Krawczyk et al., "HMAC: Keyed-Hashing for Message Authentication",
|
||
|
# RFC 2104, February 1997
|
||
|
#
|
||
|
# These APIs are inspired by JCE 1.2's javax.crypto.Mac interface.
|
||
|
#
|
||
|
# <URL:http://java.sun.com/security/JCE1.2/spec/apidoc/javax/crypto/Mac.html>
|
||
|
|
||
|
module HMAC
|
||
|
class Base
|
||
|
def initialize(algorithm, block_size, output_length, key)
|
||
|
@algorithm = algorithm
|
||
|
@block_size = block_size
|
||
|
@output_length = output_length
|
||
|
@status = STATUS_UNDEFINED
|
||
|
@key_xor_ipad = ''
|
||
|
@key_xor_opad = ''
|
||
|
set_key(key) unless key.nil?
|
||
|
end
|
||
|
|
||
|
private
|
||
|
def check_status
|
||
|
unless @status == STATUS_INITIALIZED
|
||
|
raise RuntimeError,
|
||
|
"The underlying hash algorithm has not yet been initialized."
|
||
|
end
|
||
|
end
|
||
|
|
||
|
public
|
||
|
def set_key(key)
|
||
|
# If key is longer than the block size, apply hash function
|
||
|
# to key and use the result as a real key.
|
||
|
key = @algorithm.digest(key) if key.size > @block_size
|
||
|
key_xor_ipad = "\x36" * @block_size
|
||
|
key_xor_opad = "\x5C" * @block_size
|
||
|
for i in 0 .. key.size - 1
|
||
|
key_xor_ipad[i] ^= key[i]
|
||
|
key_xor_opad[i] ^= key[i]
|
||
|
end
|
||
|
@key_xor_ipad = key_xor_ipad
|
||
|
@key_xor_opad = key_xor_opad
|
||
|
@md = @algorithm.new
|
||
|
@status = STATUS_INITIALIZED
|
||
|
end
|
||
|
|
||
|
def reset_key
|
||
|
@key_xor_ipad.gsub!(/./, '?')
|
||
|
@key_xor_opad.gsub!(/./, '?')
|
||
|
@key_xor_ipad[0..-1] = ''
|
||
|
@key_xor_opad[0..-1] = ''
|
||
|
@status = STATUS_UNDEFINED
|
||
|
end
|
||
|
|
||
|
def update(text)
|
||
|
check_status
|
||
|
# perform inner H
|
||
|
md = @algorithm.new
|
||
|
md.update(@key_xor_ipad)
|
||
|
md.update(text)
|
||
|
str = md.digest
|
||
|
# perform outer H
|
||
|
md = @algorithm.new
|
||
|
md.update(@key_xor_opad)
|
||
|
md.update(str)
|
||
|
@md = md
|
||
|
end
|
||
|
alias << update
|
||
|
|
||
|
def digest
|
||
|
check_status
|
||
|
@md.digest
|
||
|
end
|
||
|
|
||
|
def hexdigest
|
||
|
check_status
|
||
|
@md.hexdigest
|
||
|
end
|
||
|
alias to_s hexdigest
|
||
|
|
||
|
# These two class methods below are safer than using above
|
||
|
# instance methods combinatorially because an instance will have
|
||
|
# held a key even if it's no longer in use.
|
||
|
def Base.digest(key, text)
|
||
|
begin
|
||
|
hmac = self.new(key)
|
||
|
hmac.update(text)
|
||
|
hmac.digest
|
||
|
ensure
|
||
|
hmac.reset_key
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def Base.hexdigest(key, text)
|
||
|
begin
|
||
|
hmac = self.new(key)
|
||
|
hmac.update(text)
|
||
|
hmac.hexdigest
|
||
|
ensure
|
||
|
hmac.reset_key
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private_class_method :new, :digest, :hexdigest
|
||
|
end
|
||
|
|
||
|
STATUS_UNDEFINED, STATUS_INITIALIZED = 0, 1
|
||
|
end
|