summaryrefslogtreecommitdiffstats
path: root/vendor/gems/ruby-openid-2.1.4/lib/hmac/hmac.rb
blob: e8bfa42bcaa7a8f4765cc6518852d8f619446a24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# 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