hkdf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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