1# == License
2#
3# Copyright (c) 2006 Akinori MUSHA <knu@iDaemons.org>
4#
5# Documentation by Akinori MUSHA
6#
7# All rights reserved.  You can redistribute and/or modify it under
8# the same terms as Ruby.
9#
10#   $Id: hmac.rb 31594 2011-05-16 20:52:55Z drbrain $
11#
12
13warn "use of the experimetal library 'digest/hmac' is discouraged; require 'openssl' and use OpenSSL::HMAC instead." if $VERBOSE
14
15require 'digest'
16
17module Digest
18  # = digest/hmac.rb
19  #
20  # An experimental implementation of HMAC keyed-hashing algorithm
21  #
22  # == Overview
23  #
24  # CAUTION: Use of this library is discouraged, because this
25  # implementation was meant to be experimental but somehow got into the
26  # 1.9 series without being noticed.  Please use OpenSSL::HMAC in the
27  # "openssl" library instead.
28  #
29  # == Examples
30  #
31  #   require 'digest/hmac'
32  #
33  #   # one-liner example
34  #   puts Digest::HMAC.hexdigest("data", "hash key", Digest::SHA1)
35  #
36  #   # rather longer one
37  #   hmac = Digest::HMAC.new("foo", Digest::RMD160)
38  #
39  #   buf = ""
40  #   while stream.read(16384, buf)
41  #     hmac.update(buf)
42  #   end
43  #
44  #   puts hmac.bubblebabble
45  #
46  class HMAC < Digest::Class
47
48    # Creates a Digest::HMAC instance.
49
50    def initialize(key, digester)
51      @md = digester.new
52
53      block_len = @md.block_length
54
55      if key.bytesize > block_len
56        key = @md.digest(key)
57      end
58
59      ipad = Array.new(block_len, 0x36)
60      opad = Array.new(block_len, 0x5c)
61
62      key.bytes.each_with_index { |c, i|
63        ipad[i] ^= c
64        opad[i] ^= c
65      }
66
67      @key = key.freeze
68      @ipad = ipad.pack('C*').freeze
69      @opad = opad.pack('C*').freeze
70      @md.update(@ipad)
71    end
72
73    def initialize_copy(other) # :nodoc:
74      @md = other.instance_eval { @md.clone }
75    end
76
77    # call-seq:
78    #   hmac.update(string) -> hmac
79    #   hmac << string -> hmac
80    #
81    # Updates the hmac using a given +string+ and returns self.
82    def update(text)
83      @md.update(text)
84      self
85    end
86    alias << update
87
88    # call-seq:
89    #   hmac.reset -> hmac
90    #
91    # Resets the hmac to the initial state and returns self.
92    def reset
93      @md.reset
94      @md.update(@ipad)
95      self
96    end
97
98    def finish # :nodoc:
99      d = @md.digest!
100      @md.update(@opad)
101      @md.update(d)
102      @md.digest!
103    end
104    private :finish
105
106    # call-seq:
107    #   hmac.digest_length -> Integer
108    #
109    # Returns the length in bytes of the hash value of the digest.
110    def digest_length
111      @md.digest_length
112    end
113
114    # call-seq:
115    #   hmac.block_length -> Integer
116    #
117    # Returns the block length in bytes of the hmac.
118    def block_length
119      @md.block_length
120    end
121
122    # call-seq:
123    #   hmac.inspect -> string
124    #
125    # Creates a printable version of the hmac object.
126    def inspect
127      sprintf('#<%s: key=%s, digest=%s>', self.class.name, @key.inspect, @md.inspect.sub(/^\#<(.*)>$/) { $1 });
128    end
129  end
130end
131
132if $0 == __FILE__
133  eval DATA.gets(nil), nil, $0, DATA.lineno
134end
135
136__END__
137
138require 'test/unit'
139
140module TM_HMAC
141  def test_s_hexdigest
142    cases.each { |h|
143      digesters.each { |d|
144        assert_equal(h[:hexdigest], Digest::HMAC.hexdigest(h[:data], h[:key], d))
145      }
146    }
147  end
148
149  def test_hexdigest
150    cases.each { |h|
151      digesters.each { |d|
152        hmac = Digest::HMAC.new(h[:key], d)
153
154        hmac.update(h[:data])
155
156        assert_equal(h[:hexdigest], hmac.hexdigest)
157      }
158    }
159  end
160
161  def test_reset
162    cases.each { |h|
163      digesters.each { |d|
164        hmac = Digest::HMAC.new(h[:key], d)
165        hmac.update("test")
166        hmac.reset
167        hmac.update(h[:data])
168
169        assert_equal(h[:hexdigest], hmac.hexdigest)
170      }
171    }
172  end
173end
174
175class TC_HMAC_MD5 < Test::Unit::TestCase
176  include TM_HMAC
177
178  def digesters
179    [Digest::MD5, Digest::MD5.new]
180  end
181
182  # Taken from RFC 2202: Test Cases for HMAC-MD5 and HMAC-SHA-1
183  def cases
184    [
185     {
186       :key		=> "\x0b" * 16,
187       :data		=> "Hi There",
188       :hexdigest	=> "9294727a3638bb1c13f48ef8158bfc9d",
189     }, {
190       :key		=> "Jefe",
191       :data		=> "what do ya want for nothing?",
192       :hexdigest	=> "750c783e6ab0b503eaa86e310a5db738",
193     }, {
194       :key		=> "\xaa" * 16,
195       :data		=> "\xdd" * 50,
196       :hexdigest	=> "56be34521d144c88dbb8c733f0e8b3f6",
197     }, {
198       :key		=> "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19",
199       :data		=> "\xcd" * 50,
200       :hexdigest	=> "697eaf0aca3a3aea3a75164746ffaa79",
201     }, {
202       :key		=> "\x0c" * 16,
203       :data		=> "Test With Truncation",
204       :hexdigest	=> "56461ef2342edc00f9bab995690efd4c",
205     }, {
206       :key		=> "\xaa" * 80,
207       :data		=> "Test Using Larger Than Block-Size Key - Hash Key First",
208       :hexdigest	=> "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd",
209     }, {
210       :key		=> "\xaa" * 80,
211       :data		=> "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
212       :hexdigest	=> "6f630fad67cda0ee1fb1f562db3aa53e",
213     }
214    ]
215  end
216end
217
218class TC_HMAC_SHA1 < Test::Unit::TestCase
219  include TM_HMAC
220
221  def digesters
222    [Digest::SHA1, Digest::SHA1.new]
223  end
224
225  # Taken from RFC 2202: Test Cases for HMAC-MD5 and HMAC-SHA-1
226  def cases
227    [
228     {
229       :key		=> "\x0b" * 20,
230       :data		=> "Hi There",
231       :hexdigest	=> "b617318655057264e28bc0b6fb378c8ef146be00",
232     }, {
233       :key		=> "Jefe",
234       :data		=> "what do ya want for nothing?",
235       :hexdigest	=> "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
236     }, {
237       :key		=> "\xaa" * 20,
238       :data		=> "\xdd" * 50,
239       :hexdigest	=> "125d7342b9ac11cd91a39af48aa17b4f63f175d3",
240     }, {
241       :key		=> "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19",
242       :data		=> "\xcd" * 50,
243       :hexdigest	=> "4c9007f4026250c6bc8414f9bf50c86c2d7235da",
244     }, {
245       :key		=> "\x0c" * 20,
246       :data		=> "Test With Truncation",
247       :hexdigest	=> "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
248     }, {
249       :key		=> "\xaa" * 80,
250       :data		=> "Test Using Larger Than Block-Size Key - Hash Key First",
251       :hexdigest	=> "aa4ae5e15272d00e95705637ce8a3b55ed402112",
252     }, {
253       :key		=> "\xaa" * 80,
254       :data		=> "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
255       :hexdigest	=> "e8e99d0f45237d786d6bbaa7965c7808bbff1a91",
256     }
257    ]
258  end
259end
260
261class TC_HMAC_RMD160 < Test::Unit::TestCase
262  include TM_HMAC
263
264  def digesters
265    [Digest::RMD160, Digest::RMD160.new]
266  end
267
268  # Taken from RFC 2286: Test Cases for HMAC-RIPEMD160 and HMAC-RIPEMD128
269  def cases
270    [
271     {
272       :key		=> "\x0b" * 20,
273       :data		=> "Hi There",
274       :hexdigest	=> "24cb4bd67d20fc1a5d2ed7732dcc39377f0a5668",
275     }, {
276       :key		=> "Jefe",
277       :data		=> "what do ya want for nothing?",
278       :hexdigest	=> "dda6c0213a485a9e24f4742064a7f033b43c4069",
279     }, {
280       :key		=> "\xaa" * 20,
281       :data		=> "\xdd" * 50,
282       :hexdigest	=> "b0b105360de759960ab4f35298e116e295d8e7c1",
283     }, {
284       :key		=> "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19",
285       :data		=> "\xcd" * 50,
286       :hexdigest	=> "d5ca862f4d21d5e610e18b4cf1beb97a4365ecf4",
287     }, {
288       :key		=> "\x0c" * 20,
289       :data		=> "Test With Truncation",
290       :hexdigest	=> "7619693978f91d90539ae786500ff3d8e0518e39",
291     }, {
292       :key		=> "\xaa" * 80,
293       :data		=> "Test Using Larger Than Block-Size Key - Hash Key First",
294       :hexdigest	=> "6466ca07ac5eac29e1bd523e5ada7605b791fd8b",
295     }, {
296       :key		=> "\xaa" * 80,
297       :data		=> "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
298       :hexdigest	=> "69ea60798d71616cce5fd0871e23754cd75d5a0a",
299     }
300    ]
301  end
302end
303