hkdf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ # HKDF [![Build Status](https://secure.travis-ci.org/jtdowney/hkdf.png?branch=master)](http://travis-ci.org/jtdowney/hkdf)
2
+
3
+ This is a ruby implementation of [RFC 5869](http://tools.ietf.org/html/rfc5869): HMAC-based Extract-and-Expand Key Derivation Function. The goal of HKDF is to take some source key material and generate suitable cryptographic keys from it.
4
+
5
+ ## Usage
6
+
7
+ ```ruby
8
+ hkdf = HKDF.new('source key material')
9
+ hkdf.next_bytes(32)
10
+ => "\f#\xF4b\x98\x9B\x7Fw>|/|k\xF4k\xB7\xB9\x11e\xC5\x92\xD1\fH\xFDG\x94vt\xB4\x14\xCE"
11
+ ```
12
+
13
+ The default algorithm is HMAC-SHA256, you can override this and other defaults by providing an options hash during construction.
14
+
15
+ ```ruby
16
+ hkdf = HKDF.new('source key material', :salt => 'NaCl', :algorithm => 'SHA1', :info => 'the 411')
17
+ hkdf.next_bytes(16)
18
+ => "\xC0<\x13\x85\x8C\x84z\xCE\xC7\xCE+\xFF\x1C\xEB\xE6\xBC"
19
+ ```
20
+
21
+ ## License
22
+
23
+ The HKDF gem is released under the [MIT license](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,60 @@
1
+ require 'openssl'
2
+
3
+ class HKDF
4
+ def initialize(source, options = {})
5
+ options = {:algorithm => 'SHA256', :info => '', :salt => nil}.merge(options)
6
+
7
+ @digest = OpenSSL::Digest.new(options[:algorithm])
8
+ @info = options[:info]
9
+
10
+ salt = options[:salt]
11
+ salt = 0.chr * @digest.digest_length if salt.nil? or salt.empty?
12
+
13
+ @prk = OpenSSL::HMAC.digest(@digest, salt, source)
14
+ @position = 0
15
+ @blocks = []
16
+ @blocks << ''
17
+ end
18
+
19
+ def algorithm
20
+ @digest.name
21
+ end
22
+
23
+ def max_length
24
+ @digest.digest_length * 255
25
+ end
26
+
27
+ def seek(position)
28
+ raise RangeError.new("cannot seek past #{max_length}") if position > max_length
29
+
30
+ @position = position
31
+ end
32
+
33
+ def rewind
34
+ seek(0)
35
+ end
36
+
37
+ def next_bytes(length)
38
+ new_position = length + @position
39
+ raise RangeError.new("requested #{length} bytes, only #{max_length} available") if new_position > max_length
40
+
41
+ _generate_blocks(new_position)
42
+
43
+ start = @position
44
+ @position = new_position
45
+
46
+ @blocks.join('').slice(start, length)
47
+ end
48
+
49
+ def next_hex_bytes(length)
50
+ next_bytes(length).unpack('H*').first
51
+ end
52
+
53
+ def _generate_blocks(length)
54
+ start = @blocks.size
55
+ block_count = (length.to_f / @digest.digest_length).ceil
56
+ start.upto(block_count) do |n|
57
+ @blocks << OpenSSL::HMAC.digest(@digest, @prk, @blocks[n - 1] + @info + n.chr)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe HKDF do
4
+ before(:each) do
5
+ @algorithm = 'SHA256'
6
+ @source = 'source'
7
+ @hkdf = HKDF.new(@source, :algorithm => @algorithm)
8
+ end
9
+
10
+ describe 'initialize' do
11
+ it 'defaults the algorithm to SHA-256' do
12
+ HKDF.new(@source).algorithm.should == 'SHA256'
13
+ end
14
+
15
+ it 'takes an optional digest algorithm' do
16
+ @hkdf = HKDF.new('source', :algorithm => 'SHA1')
17
+ @hkdf.algorithm.should == 'SHA1'
18
+ end
19
+
20
+ it 'defaults salt to all zeros of digest length' do
21
+ salt = 0.chr * 32
22
+
23
+ @hkdf_salt = HKDF.new(@source, :algorithm => @algorithm, :salt => salt)
24
+ @hkdf_nosalt = HKDF.new(@source, :algorithm => @algorithm)
25
+ @hkdf_salt.next_bytes(32) == @hkdf_nosalt.next_bytes(32)
26
+ end
27
+
28
+ it 'sets salt to all zeros of digest 32 if empty' do
29
+ @hkdf_blanksalt = HKDF.new(@source, :algorithm => @algorithm, :salt => '')
30
+ @hkdf_nosalt = HKDF.new(@source, :algorithm => @algorithm)
31
+ @hkdf_blanksalt.next_bytes(32) == @hkdf_nosalt.next_bytes(32)
32
+ end
33
+
34
+ it 'defaults info to an empty string' do
35
+ @hkdf_info = HKDF.new(@source, :algorithm => @algorithm, :info => '')
36
+ @hkdf_noinfo = HKDF.new(@source, :algorithm => @algorithm)
37
+ @hkdf_info.next_bytes(32) == @hkdf_noinfo.next_bytes(32)
38
+ end
39
+ end
40
+
41
+ describe 'max_length' do
42
+ it 'is 255 times the digest length' do
43
+ @hkdf.max_length.should == 255 * 32
44
+ end
45
+ end
46
+
47
+ describe 'next_bytes' do
48
+ it 'raises an error if requested size is > max_length' do
49
+ expect { @hkdf.next_bytes(@hkdf.max_length + 1) }.should raise_error(RangeError, /requested \d+ bytes, only \d+ available/)
50
+ expect { @hkdf.next_bytes(@hkdf.max_length) }.should_not raise_error(RangeError, /requested \d+ bytes, only \d+ available/)
51
+ end
52
+
53
+ it 'raises an error if requested size + current position is > max_length' do
54
+ expect do
55
+ @hkdf.next_bytes(32)
56
+ @hkdf.next_bytes(@hkdf.max_length - 31)
57
+ end.should raise_error(RangeError, /requested \d+ bytes, only \d+ available/)
58
+ end
59
+
60
+ it 'advances the stream position' do
61
+ @hkdf.next_bytes(32).should_not == @hkdf.next_bytes(32)
62
+ end
63
+
64
+ test_vectors.each do |name, options|
65
+ it "matches output from the '#{name}' test vector" do
66
+ options[:algorithm] = options[:Hash]
67
+
68
+ hkdf = HKDF.new(options[:IKM], options)
69
+ hkdf.next_bytes(options[:L].to_i).should == options[:OKM]
70
+ end
71
+ end
72
+ end
73
+
74
+ describe 'next_hex_bytes' do
75
+ it 'returns the next bytes as hex' do
76
+ @hkdf.next_hex_bytes(20).should == 'fb496612b8cb82cd2297770f83c72b377af16d7b'
77
+ end
78
+ end
79
+
80
+ describe 'seek' do
81
+ it 'sets the position anywhere in the stream' do
82
+ @hkdf.next_bytes(10)
83
+ output = @hkdf.next_bytes(32)
84
+ @hkdf.seek(10)
85
+ @hkdf.next_bytes(32).should == output
86
+ end
87
+
88
+ it 'raises an error if requested to seek past end of stream' do
89
+ expect { @hkdf.seek(@hkdf.max_length + 1) }.should raise_error(RangeError, /cannot seek past \d+/)
90
+ expect { @hkdf.seek(@hkdf.max_length) }.should_not raise_error(RangeError, /cannot seek past \d+/)
91
+ end
92
+ end
93
+
94
+ describe 'rewind' do
95
+ it 'resets the stream position to the beginning' do
96
+ output = @hkdf.next_bytes(32)
97
+ @hkdf.rewind
98
+ @hkdf.next_bytes(32).should == output
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,19 @@
1
+ require 'hkdf'
2
+
3
+ def test_vectors
4
+ test_lines = File.readlines('spec/test_vectors.txt').map(&:strip).reject(&:empty?)
5
+
6
+ vectors = {}
7
+ test_lines.each_slice(8) do |lines|
8
+ name = lines.shift
9
+ values = lines.inject({}) do |hash, line|
10
+ key, value = line.split('=').map(&:strip)
11
+ value = '' unless value
12
+ value = [value.slice(2..-1)].pack('H*') if value.start_with?('0x')
13
+ hash[key.to_sym] = value
14
+ hash
15
+ end
16
+ vectors[name] = values
17
+ end
18
+ vectors
19
+ end
@@ -0,0 +1,53 @@
1
+ Basic test case with SHA256
2
+ Hash = SHA256
3
+ IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
4
+ salt = 0x000102030405060708090a0b0c
5
+ info = 0xf0f1f2f3f4f5f6f7f8f9
6
+ L = 42
7
+ PRK = 0x077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5
8
+ OKM = 0x3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865
9
+
10
+ Test with SHA256 and longer inputs/outputs
11
+ Hash = SHA256
12
+ IKM = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f
13
+ salt = 0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
14
+ info = 0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
15
+ L = 82
16
+ PRK = 0x06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244
17
+ OKM = 0xb11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87
18
+
19
+ Test with SHA256 and empty salt/info
20
+ Hash = SHA256
21
+ IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
22
+ salt =
23
+ info =
24
+ L = 42
25
+ PRK = 0x19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04
26
+ OKM = 0x8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8
27
+
28
+ Basic test case with SHA1
29
+ Hash = SHA1
30
+ IKM = 0x0b0b0b0b0b0b0b0b0b0b0b
31
+ salt = 0x000102030405060708090a0b0c
32
+ info = 0xf0f1f2f3f4f5f6f7f8f9
33
+ L = 42
34
+ PRK = 0x9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243
35
+ OKM = 0x085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896
36
+
37
+ Test with SHA1 and longer inputs/outputs
38
+ Hash = SHA1
39
+ IKM = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f
40
+ salt = 0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
41
+ info = 0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
42
+ L = 82
43
+ PRK = 0x8adae09a2a307059478d309b26c4115a224cfaf6
44
+ OKM = 0x0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4
45
+
46
+ Test with SHA1 and empty salt/info
47
+ Hash = SHA1
48
+ IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
49
+ salt =
50
+ info =
51
+ L = 42
52
+ PRK = 0xda8c8a73c7fa77288ec6f5e7c297786aa0d32d01
53
+ OKM = 0x0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hkdf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Downey
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70196819801820 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70196819801820
25
+ description: ! 'A ruby implementation of RFC5869: HMAC-based Extract-and-Expand Key
26
+ Derivation Function (HKDF). The goal of HKDF is to take some source key material
27
+ and generate suitable cryptographic keys from it.'
28
+ email:
29
+ - jdowney@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/hkdf.rb
35
+ - README.md
36
+ - spec/hkdf_spec.rb
37
+ - spec/spec_helper.rb
38
+ - spec/test_vectors.txt
39
+ homepage: http://github.com/jtdowney/hkdf
40
+ licenses: []
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project:
59
+ rubygems_version: 1.8.17
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: HMAC-based Key Derivation Function
63
+ test_files:
64
+ - spec/hkdf_spec.rb
65
+ - spec/spec_helper.rb
66
+ - spec/test_vectors.txt