pbkdf2-ruby 0.2.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,21 @@
1
+ Copyright (c) 2008 Sam Quigley <quigley@emerose.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
21
+ This license is sometimes referred to as "The MIT License"
@@ -0,0 +1,158 @@
1
+ # PBKDF2 #
2
+
3
+ A Ruby implementation of the [Password-Based Key-Derivation Function, uh,
4
+ 2][PBKDF2].
5
+
6
+ ## Using PBKDF2 ##
7
+
8
+ The basic steps are:
9
+
10
+ 1. Instantiate a `PBKDF2` object
11
+ 2. Call `#bin_string` (or `#hex_string`) on it to recover the binary (hex) form of the key
12
+
13
+ To instantiate, you can either pass the required arguments as options in a
14
+ hash, like so:
15
+
16
+ PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>10000)
17
+
18
+ or use the (easier and prettier, in my view) builder idiom:
19
+
20
+ PBKDF2.new do |p|
21
+ p.password = "s33krit"
22
+ p.salt = "nacl"
23
+ p.iterations = 10000
24
+ end
25
+
26
+ You can also mix-and-match ways of passing arguments, but I don't know why
27
+ you'd want to do that.
28
+
29
+ ### Required options ###
30
+
31
+ A `PBKDF2` object cannot be instantiated without setting the following options:
32
+
33
+ * **`password`**: The passphrase used for the key, passed as a (possibly
34
+ binary) string.
35
+
36
+ This should be kept secret, preferably nowhere but the end-user's memory.
37
+
38
+ * **`salt`**: A salt for this passphrase, passed as a (possibly binary)
39
+ string.
40
+
41
+ This does not need to be kept secret, but should be made as long as is
42
+ reasonable (64-128 bits) to avoid precomputed image ("rainbow table")
43
+ attacks.
44
+
45
+ * **`iterations`**: The number of iterated hashes to calculate, expressed as
46
+ an integer.
47
+
48
+ This does not need to be kept secret, but should be made as large as is
49
+ reasonable. See below for guidance on choosing a good number for this.
50
+
51
+ PBKDF2 objects can also be configured with the following options:
52
+
53
+ * **`hash_function`**: The hashing algorithm to be used.
54
+
55
+ This option may be expressed in a number of ways:
56
+ * As a `Class` object, such as `OpenSSL::Digest::SHA512`. Only OpenSSL
57
+ digest classes are supported at the moment.
58
+ * As an already-instantiated OpenSSL digest object, such as the result of
59
+ `OpenSSL::Digest::SHA512.new`. If you use this method, take care that
60
+ the hash object is in its just-initialized state (or that the same hash
61
+ object with the same state is used whenever keys are generated/checked).
62
+ * As a string which is understood by `OpenSSL::Digest::Digest.new()`.
63
+ Things like "sha1", "md5", "RIPEMD160", etc. all work fine. If the
64
+ string begins with the text "hmacWith" it will be stripped before
65
+ passing it to the underlying OpenSSL library, making it possible to use
66
+ arguments that at least look more like the ASN.1 identifiers in the
67
+ spec.
68
+ * A symbol, like `:sha256`, that, when converted to a string, meets the
69
+ rules for strings above.
70
+
71
+ If not specified, SHA-256 will be used. (Note that other implementations
72
+ may default to SHA-1.)
73
+
74
+ * **`key_length`**: The length, in bytes, of the key you wish to generate.
75
+
76
+ By default, the key generated will be equal in length to the hash output
77
+ size. This can be adjusted to any size required, up to `((2**32 - 1) * (hash
78
+ length)`.
79
+
80
+ If a required parameter is missing, or if an invalid parameter is passed to one of these options, an `ArgumentError` exception will be raised.
81
+
82
+ ## Setting the Number of Iterations ##
83
+
84
+ The `iterations` option exposed by PBKDF2 provides a way of controlling the
85
+ amount of work required to check a candidate passphrase. It can be thought of
86
+ as a work factor governing the amount of work an attacker must do in order to
87
+ perform a dictionary or brute-force attack on passwords. Unfortunately, it
88
+ also governs the amount of work that must be performed on behalf of legitimate
89
+ users must when checking credentials.
90
+
91
+ Choosing the correct value for this parameter is thus a balancing act: it
92
+ should be set as high as possible, to make attacks as difficult as possible,
93
+ without making legitimate applications unusably slow. One method for choosing
94
+ a value is based on estimating an upper bound on the resources an attacker is
95
+ likely to have available, and then finding an iteration count that makes such
96
+ attacks unprofitable. A useful example of this sort of reasoning can be found
97
+ in the [Security Considerations section of RFC 3962][ITERS].
98
+
99
+ The other approach for choosing the iterations count is to decide the maximum
100
+ performance penalty that can be tolerated in the context of the application,
101
+ and to set the iteration count so that it remains within these bounds. The
102
+ `PBKDF2` module contains a `benchmark` method for this purpose: to use it,
103
+ instantiate a `PBKDF2` object as normal, using the `hash_function` and
104
+ `key_length` you intend to use in the final application. Then, call the
105
+ `benchmark` method on the object: the result will be the time, in seconds,
106
+ required to complete one iteration. Divide the maximum performance penalty
107
+ by this number to find the number of iterations you should choose.
108
+
109
+ The first method requires implementors to estimate a number of important
110
+ variables, including the resources available to attackers, which may be
111
+ difficult or impossible to do well. The second method is also prone to error,
112
+ as it can be difficult to predict load characteristics in production
113
+ conditions, or the impact of a few milliseconds' delay on end-user
114
+ perceptions. The best approach will necessarily involve trying both
115
+ approaches and balancing the competing concerns against one-another.
116
+
117
+ Note that no default for this option is provided, as a way of forcing
118
+ implementors to consider this issue in their own contexts. Anyone who, having
119
+ read and understood the above, is still unsure what the value to choose should
120
+ just use 5,000 and move on.
121
+
122
+ ## Relevant Standards ##
123
+
124
+ PBKDF2 was originally defined as part of RSA Laboratories' [PKCS #5][PKCS],
125
+ part of their Public-Key Cryptography Standards series. It has since been
126
+ republished as [RFC 2898][RFC].
127
+
128
+ ### Standards Conformance ###
129
+
130
+ This implementation conforms to [RFC 2898][RFC], and has been tested using the
131
+ test vectors in Appendix B of [RFC 3962][3962]. Note, however, that while
132
+ those specifications use [HMAC][HMAC]-[SHA-1][SHA1], **this implementation
133
+ defaults to [HMAC][HMAC]-[SHA-256][SHA1]**. (SHA-256 provides a longer bit
134
+ length. In addition, NIST has [stated][NIST] that SHA-1 should be phased out
135
+ due to concerns over recent cryptanalytic attacks.)
136
+
137
+ ## TODO ##
138
+
139
+ This version is essentially complete. If ASN.1 weren't such a nightmare, it
140
+ might be useful to be able to initialize `PBKDF2` objects based on standard
141
+ OIDs for parameters. It would also be nice to have a standard envelope for
142
+ serializing sets of {key, salt, options}. Both of these are probably tasks
143
+ for other modules, however. (YAML fits the bill pretty well already.)
144
+
145
+ ## License ##
146
+
147
+ This software is ©2008 Sam Quigley <quigley@emerose.com>. See the
148
+ LICENSE.TXT file accompanying this document for the terms under which it may
149
+ be used and distributed.
150
+
151
+ [PBKDF2]: http://en.wikipedia.org/wiki/PBKDF2 "Wikipedia: PBKDF2"
152
+ [PKCS]: http://www.rsa.com/rsalabs/node.asp?id=2127 "PKCS #5"
153
+ [RFC]: http://tools.ietf.org/html/rfc2898 "RFC 2898"
154
+ [3962]: http://tools.ietf.org/html/rfc3962 "RFC 3962"
155
+ [SHA1]: http://en.wikipedia.org/wiki/SHA-1 "Wikipedia: SHA-1"
156
+ [HMAC]: http://tools.ietf.org/html/rfc2104 "RFC 2104"
157
+ [ITERS]: http://tools.ietf.org/html/rfc3962#page-6 "RFC 3962: Section 8"
158
+ [NIST]: http://csrc.nist.gov/groups/ST/hash/statement.html "NIST Comments on Cryptanalytic Attacks on SHA-1"
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "pbkdf2-ruby"
8
+ gem.summary = %Q{Password-Based Key Derivation Function 2 - PBKDF2}
9
+ gem.description = %Q{This implementation conforms to RFC 2898, and has been tested using the test vectors in Appendix B of RFC 3962. Note, however, that while those specifications use HMAC-SHA-1, this implementation defaults to HMAC-SHA-256. (SHA-256 provides a longer bit length. In addition, NIST has stated that SHA-1 should be phased out due to concerns over recent cryptanalytic attacks.)}
10
+ gem.email = "quigley@emerose.com"
11
+ gem.homepage = "http://github.com/emerose/pbkdf2-ruby"
12
+ gem.authors = ["Sam Quigley"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "rdoc", ">= 2.4.2"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rspec/core/rake_task'
23
+
24
+ RSpec::Core::RakeTask.new(:spec) do |spec|
25
+ spec.rspec_opts = ["--color", "--format progress", "-r ./spec/spec_helper.rb"]
26
+ spec.pattern = 'spec/**/*_spec.rb'
27
+ end
28
+
29
+ task :spec => :check_dependencies
30
+
31
+ task :default => :spec
32
+
33
+ require 'rdoc/task'
34
+ RDoc::Task.new do |rdoc|
35
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
36
+
37
+ rdoc.rdoc_dir = 'rdoc'
38
+ rdoc.title = "pbkdf2 #{version}"
39
+ rdoc.rdoc_files.include('README*')
40
+ rdoc.rdoc_files.include('lib/**/*.rb')
41
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,33 @@
1
+ require 'benchmark'
2
+ require 'lib/pbkdf2'
3
+
4
+ n = 100000
5
+ #
6
+ # from active-ldap
7
+ module Salt
8
+ CHARS = ['.', '/', '0'..'9', 'A'..'Z', 'a'..'z'].collect do |x|
9
+ x.to_a
10
+ end.flatten
11
+ module_function
12
+ def generate(length)
13
+ salt = ""
14
+ length.times {salt << CHARS[rand(CHARS.length)]}
15
+ salt
16
+ end
17
+ end
18
+
19
+ def next_salt
20
+ Salt.generate(8)
21
+ end
22
+ Benchmark.bm do |x|
23
+ x.report do
24
+ 1.upto(n) do
25
+ PBKDF2.new do |p|
26
+ p.password = "s33krit"
27
+ p.salt = next_salt
28
+ p.iterations = 1000
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,168 @@
1
+ require 'openssl'
2
+
3
+ class PBKDF2
4
+ def initialize(opts={})
5
+ @hash_function = OpenSSL::Digest::Digest.new("sha256")
6
+
7
+ # override with options
8
+ opts.each_key do |k|
9
+ if self.respond_to?("#{k}=")
10
+ self.send("#{k}=", opts[k])
11
+ else
12
+ raise ArgumentError, "Argument '#{k}' is not allowed"
13
+ end
14
+ end
15
+
16
+ yield self if block_given?
17
+
18
+ # set this to the default if nothing was given
19
+ @key_length ||= @hash_function.size
20
+
21
+ # make sure the relevant things got set
22
+ raise ArgumentError, "password not set" if @password.nil?
23
+ raise ArgumentError, "salt not set" if @salt.nil?
24
+ raise ArgumentError, "iterations not set" if @iterations.nil?
25
+ end
26
+ attr_reader :key_length, :hash_function, :iterations, :salt, :password
27
+
28
+ def key_length=(l)
29
+ raise ArgumentError, "key too short" if l < 1
30
+ raise ArgumentError, "key too long" if l > ((2**32 - 1) * @hash_function.size)
31
+ @value = nil
32
+ @key_length = l
33
+ end
34
+
35
+ def hash_function=(h)
36
+ @value = nil
37
+ @hash_function = find_hash(h)
38
+ end
39
+
40
+ def iterations=(i)
41
+ raise ArgumentError, "iterations can't be less than 1" if i < 1
42
+ @value = nil
43
+ @iterations = i
44
+ end
45
+
46
+ def salt=(s)
47
+ @value = nil
48
+ @salt = s
49
+ end
50
+
51
+ def password=(p)
52
+ @value = nil
53
+ @password = p
54
+ end
55
+
56
+ def value
57
+ calculate! if @value.nil?
58
+ @value
59
+ end
60
+
61
+ alias bin_string value
62
+
63
+ def hex_string
64
+ bin_string.unpack("H*").first
65
+ end
66
+
67
+ # return number of milliseconds it takes to complete one iteration
68
+ def benchmark(iters = 400000)
69
+ iter_orig = @iterations
70
+ @iterations=iters
71
+ start = Time.now
72
+ calculate!
73
+ time = Time.now - start
74
+ @iterations = iter_orig
75
+ return (time/iters)
76
+ end
77
+
78
+ protected
79
+
80
+ # finds and instantiates, if necessary, a hash function
81
+ def find_hash(hash)
82
+ case hash
83
+ when Class
84
+ # allow people to pass in classes to be instantiated
85
+ # (eg, pass in OpenSSL::Digest::SHA1)
86
+ hash = find_hash(hash.new)
87
+ when Symbol
88
+ # convert symbols to strings and see if OpenSSL::Digest can make sense of
89
+ hash = find_hash(hash.to_s)
90
+ when String
91
+ # if it's a string, first strip off any leading 'hmacWith' (which is implied)
92
+ hash.gsub!(/^hmacWith/i,'')
93
+ # see if the OpenSSL lib understands it
94
+ hash = OpenSSL::Digest::Digest.new(hash)
95
+ when OpenSSL::Digest
96
+ when OpenSSL::Digest::Digest
97
+ # ok
98
+ else
99
+ raise TypeError, "Unknown hash type: #{hash.class}"
100
+ end
101
+ hash
102
+ end
103
+
104
+ # the pseudo-random function defined in the spec
105
+ def prf(data)
106
+ OpenSSL::HMAC.digest(@hash_function, @password, data)
107
+ end
108
+
109
+ # this is a translation of the helper function "F" defined in the spec
110
+ def calculate_block(block_num)
111
+ # u_1:
112
+ u = prf(salt+[block_num].pack("N"))
113
+ ret = u
114
+ # u_2 through u_c:
115
+ 2.upto(@iterations) do
116
+ # calculate u_n
117
+ u = prf(u)
118
+ # xor it with the previous results
119
+ ret = ret^u
120
+ end
121
+ ret
122
+ end
123
+
124
+ # the bit that actually does the calculating
125
+ def calculate!
126
+ # how many blocks we'll need to calculate (the last may be truncated)
127
+ blocks_needed = (@key_length.to_f / @hash_function.size).ceil
128
+ # reset
129
+ @value = ""
130
+ # main block-calculating loop:
131
+ 1.upto(blocks_needed) do |block_num|
132
+ @value << calculate_block(block_num)
133
+ end
134
+ # truncate to desired length:
135
+ @value = @value.slice(0,@key_length)
136
+ @value
137
+ end
138
+ end
139
+
140
+
141
+ class String
142
+ if RUBY_VERSION >= "1.9"
143
+ def xor_impl(other)
144
+ result = "".encode("ASCII-8BIT")
145
+ o_bytes = other.bytes.to_a
146
+ bytes.each_with_index do |c, i|
147
+ result << (c ^ o_bytes[i])
148
+ end
149
+ result
150
+ end
151
+ else
152
+ def xor_impl(other)
153
+ result = (0..self.length-1).collect { |i| self[i] ^ other[i] }
154
+ result.pack("C*")
155
+ end
156
+ end
157
+
158
+ private :xor_impl
159
+
160
+ def ^(other)
161
+ raise ArgumentError, "Can't bitwise-XOR a String with a non-String" \
162
+ unless other.kind_of? String
163
+ raise ArgumentError, "Can't bitwise-XOR strings of different length" \
164
+ unless self.length == other.length
165
+
166
+ xor_impl(other)
167
+ end
168
+ end
@@ -0,0 +1,52 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "pbkdf2"
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Sam Quigley"]
12
+ s.date = "2013-10-23"
13
+ s.description = "This implementation conforms to RFC 2898, and has been tested using the test vectors in Appendix B of RFC 3962. Note, however, that while those specifications use HMAC-SHA-1, this implementation defaults to HMAC-SHA-256. (SHA-256 provides a longer bit length. In addition, NIST has stated that SHA-1 should be phased out due to concerns over recent cryptanalytic attacks.)"
14
+ s.email = "quigley@emerose.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.TXT",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ "LICENSE.TXT",
21
+ "README.markdown",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "benchmark.rb",
25
+ "lib/pbkdf2.rb",
26
+ "pbkdf2.gemspec",
27
+ "spec/pbkdf2_spec.rb",
28
+ "spec/rfc3962_spec.rb",
29
+ "spec/spec.opts",
30
+ "spec/spec_helper.rb"
31
+ ]
32
+ s.homepage = "http://github.com/emerose/pbkdf2-ruby"
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = "1.8.23"
35
+ s.summary = "Password-Based Key Derivation Function 2 - PBKDF2"
36
+
37
+ if s.respond_to? :specification_version then
38
+ s.specification_version = 3
39
+
40
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
41
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
42
+ s.add_development_dependency(%q<rdoc>, [">= 2.4.2"])
43
+ else
44
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
45
+ s.add_dependency(%q<rdoc>, [">= 2.4.2"])
46
+ end
47
+ else
48
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
49
+ s.add_dependency(%q<rdoc>, [">= 2.4.2"])
50
+ end
51
+ end
52
+
@@ -0,0 +1,144 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe PBKDF2, "when initializing" do
4
+ it "should raise an ArgumentError if an unrecognized parameter is passed" do
5
+ lambda {PBKDF2.new(:foo=>1)}.should raise_error(ArgumentError)
6
+ end
7
+
8
+ it "should raise an ArgumentError if a password is not set" do
9
+ lambda {PBKDF2.new(:salt=>"nacl", :iterations=>1000)}.should raise_error(ArgumentError)
10
+ end
11
+
12
+ it "should raise an ArgumentError if a salt is not set" do
13
+ lambda {PBKDF2.new(:password=>"s33krit", :iterations=>1000)}.should raise_error(ArgumentError)
14
+ end
15
+
16
+ it "should raise an ArgumentError if iterations is not set" do
17
+ lambda {PBKDF2.new(:password=>"s33krit", :salt=>"nacl")}.should raise_error(ArgumentError)
18
+ end
19
+
20
+ it "should allow setting options in a block" do
21
+ PBKDF2.new do |x|
22
+ x.password = "s33krit"
23
+ x.salt = "nacl"
24
+ x.iterations = 1000
25
+ end
26
+ end
27
+ end
28
+
29
+ describe PBKDF2, "when configuring a hash function" do
30
+ it "should default to SHA256" do
31
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000)
32
+ p.hash_function.name.should == "SHA256"
33
+ end
34
+
35
+ it "should support at least SHA1, SHA512, and MD5" do
36
+ %w{SHA1 SHA512 MD5}.each do |alg|
37
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000, :hash_function=>alg)
38
+ p.hash_function.name.should == alg
39
+ end
40
+ end
41
+
42
+ it "should allow setting by symbols" do
43
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000, :hash_function=>:sha512)
44
+ p.hash_function.name.should == "SHA512"
45
+ end
46
+
47
+ it "should allow setting by strings" do
48
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000, :hash_function=>"sha512")
49
+ p.hash_function.name.should == "SHA512"
50
+ end
51
+
52
+ it "should allow setting by PKCS-style 'hmacWith' strings" do
53
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000, :hash_function=>"hmacWithSHA512")
54
+ p.hash_function.name.should == "SHA512"
55
+ end
56
+
57
+ it "should allow setting by classes in OpenSSL::Digest" do
58
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000, :hash_function=>OpenSSL::Digest::SHA512)
59
+ p.hash_function.name.should == "SHA512"
60
+ end
61
+
62
+ it "should allow setting by an instantiated object of type OpenSSL::Digest::Digest" do
63
+ hfunc = OpenSSL::Digest::SHA512.new
64
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000, :hash_function=>hfunc)
65
+ p.hash_function.name.should == "SHA512"
66
+ end
67
+ end
68
+
69
+ describe PBKDF2, "when setting a key length" do
70
+ it "should default to the size of the hash function used" do
71
+ %w{SHA1 SHA512 MD5}.each do |alg|
72
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000, :hash_function=>alg)
73
+ p.key_length.should == OpenSSL::Digest::Digest.new(alg).size
74
+ end
75
+ end
76
+
77
+ it "should throw an ArgumentError if a negative length is set" do
78
+ lambda {p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1000, :key_length=>-1)}.should raise_error(ArgumentError)
79
+ end
80
+
81
+ it "should throw an ArgumentError if too long a length is set" do
82
+ not_too_long = ((2**32 - 1) * OpenSSL::Digest::SHA256.new.size)
83
+ too_long = not_too_long + 1
84
+ lambda {p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1, :key_length=>not_too_long, :hash_function=>:sha256)}.should_not raise_error
85
+ lambda {p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1, :key_length=>too_long, :hash_function=>:sha256)}.should raise_error(ArgumentError)
86
+ end
87
+
88
+ it "should ensure that the derived key really is that long" do
89
+ length = 123
90
+ p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>1, :key_length=>length)
91
+ p.bin_string.length.should == length
92
+ end
93
+ end
94
+
95
+ describe PBKDF2, "when setting the number of iterations" do
96
+ it "should throw an ArgumentError if a number less than 1 is passed" do
97
+ lambda {p = PBKDF2.new(:password=>"s33krit", :salt=>"nacl", :iterations=>0)}.should raise_error(ArgumentError)
98
+ end
99
+ end
100
+
101
+ describe PBKDF2, "once created" do
102
+ before do
103
+ @p = PBKDF2.new do |x|
104
+ x.password = "s33krit"
105
+ x.salt = "nacl"
106
+ x.iterations = 1
107
+ end
108
+ @val = @p.hex_string
109
+ end
110
+
111
+ it "should have the same hex value as another, identically derived key" do
112
+ q = PBKDF2.new do |x|
113
+ x.password = "s33krit"
114
+ x.salt = "nacl"
115
+ x.iterations = 1
116
+ end
117
+ @p.hex_string.should == q.hex_string
118
+ end
119
+
120
+ it "should recalculate the value if the password changes" do
121
+ @p.password = "foo"
122
+ @p.hex_string.should_not == @val
123
+ end
124
+
125
+ it "should recalculate the value if the salt changes" do
126
+ @p.salt = "foo"
127
+ @p.hex_string.should_not == @val
128
+ end
129
+
130
+ it "should recalculate the value if the number of iterations changes" do
131
+ @p.iterations = 2
132
+ @p.hex_string.should_not == @val
133
+ end
134
+
135
+ it "should recalculate the value if the hash function changes" do
136
+ @p.hash_function = :md5
137
+ @p.hex_string.should_not == @val
138
+ end
139
+
140
+ it "should recalculate the value if the key length changes" do
141
+ @p.key_length = 10
142
+ @p.hex_string.should_not == @val
143
+ end
144
+ end
@@ -0,0 +1,195 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe PBKDF2, "when deriving keys" do
4
+ # see http://www.rfc-archive.org/getrfc.php?rfc=3962
5
+ # all examples there use HMAC-SHA1
6
+ it "should match the first test case in appendix B of RFC 3962" do
7
+ # Iteration count = 1
8
+ # Pass phrase = "password"
9
+ # Salt = "ATHENA.MIT.EDUraeburn"
10
+ # 128-bit PBKDF2 output:
11
+ # cd ed b5 28 1b b2 f8 01 56 5a 11 22 b2 56 35 15
12
+ # 256-bit PBKDF2 output:
13
+ # cd ed b5 28 1b b2 f8 01 56 5a 11 22 b2 56 35 15
14
+ # 0a d1 f7 a0 4b b9 f3 a3 33 ec c0 e2 e1 f7 08 37
15
+ p = PBKDF2.new do |p|
16
+ p.hash_function = :sha1
17
+ p.iterations = 1
18
+ p.password = "password"
19
+ p.salt = "ATHENA.MIT.EDUraeburn"
20
+ p.key_length = 128/8
21
+ end
22
+
23
+ expected = "cd ed b5 28 1b b2 f8 01 56 5a 11 22 b2 56 35 15"
24
+ p.hex_string.should == expected.gsub(' ','')
25
+
26
+ expected = "cd ed b5 28 1b b2 f8 01 56 5a 11 22 b2 56 35 15" +
27
+ "0a d1 f7 a0 4b b9 f3 a3 33 ec c0 e2 e1 f7 08 37"
28
+
29
+ p.key_length = 256/8
30
+ p.hex_string.should == expected.gsub(' ','')
31
+ end
32
+
33
+ it "should match the second test case in appendix B of RFC 3962" do
34
+ # Iteration count = 2
35
+ # Pass phrase = "password"
36
+ # Salt="ATHENA.MIT.EDUraeburn"
37
+ # 128-bit PBKDF2 output:
38
+ # 01 db ee 7f 4a 9e 24 3e 98 8b 62 c7 3c da 93 5d
39
+ # 256-bit PBKDF2 output:
40
+ # 01 db ee 7f 4a 9e 24 3e 98 8b 62 c7 3c da 93 5d
41
+ # a0 53 78 b9 32 44 ec 8f 48 a9 9e 61 ad 79 9d 86
42
+ p = PBKDF2.new do |p|
43
+ p.hash_function = :sha1
44
+ p.iterations = 2
45
+ p.password = "password"
46
+ p.salt = "ATHENA.MIT.EDUraeburn"
47
+ p.key_length = 128/8
48
+ end
49
+
50
+ expected = "01 db ee 7f 4a 9e 24 3e 98 8b 62 c7 3c da 93 5d"
51
+ p.hex_string.should == expected.gsub(' ','')
52
+
53
+ expected = "01 db ee 7f 4a 9e 24 3e 98 8b 62 c7 3c da 93 5d" +
54
+ "a0 53 78 b9 32 44 ec 8f 48 a9 9e 61 ad 79 9d 86"
55
+ p.key_length = 256/8
56
+ p.hex_string.should == expected.gsub(' ','')
57
+ end
58
+
59
+ it "should match the third test case in appendix B of RFC 3962" do
60
+ # Iteration count = 1200
61
+ # Pass phrase = "password"
62
+ # Salt = "ATHENA.MIT.EDUraeburn"
63
+ # 128-bit PBKDF2 output:
64
+ # 5c 08 eb 61 fd f7 1e 4e 4e c3 cf 6b a1 f5 51 2b
65
+ # 256-bit PBKDF2 output:
66
+ # 5c 08 eb 61 fd f7 1e 4e 4e c3 cf 6b a1 f5 51 2b
67
+ # a7 e5 2d db c5 e5 14 2f 70 8a 31 e2 e6 2b 1e 13
68
+ p = PBKDF2.new do |p|
69
+ p.hash_function = :sha1
70
+ p.iterations = 1200
71
+ p.password = "password"
72
+ p.salt = "ATHENA.MIT.EDUraeburn"
73
+ p.key_length = 128/8
74
+ end
75
+
76
+ expected = "5c 08 eb 61 fd f7 1e 4e 4e c3 cf 6b a1 f5 51 2b"
77
+ p.hex_string.should == expected.gsub(' ','')
78
+
79
+ expected = "5c 08 eb 61 fd f7 1e 4e 4e c3 cf 6b a1 f5 51 2b" +
80
+ "a7 e5 2d db c5 e5 14 2f 70 8a 31 e2 e6 2b 1e 13"
81
+ p.key_length = 256/8
82
+ p.hex_string.should == expected.gsub(' ','')
83
+ end
84
+
85
+ it "should match the fourth test case in appendix B of RFC 3962" do
86
+ # Iteration count = 5
87
+ # Pass phrase = "password"
88
+ # Salt=0x1234567878563412
89
+ # 128-bit PBKDF2 output:
90
+ # d1 da a7 86 15 f2 87 e6 a1 c8 b1 20 d7 06 2a 49
91
+ # 256-bit PBKDF2 output:
92
+ # d1 da a7 86 15 f2 87 e6 a1 c8 b1 20 d7 06 2a 49
93
+ # 3f 98 d2 03 e6 be 49 a6 ad f4 fa 57 4b 6e 64 ee
94
+ p = PBKDF2.new do |p|
95
+ p.hash_function = :sha1
96
+ p.iterations = 5
97
+ p.password = "password"
98
+ p.salt = [0x1234567878563412].pack("Q")
99
+ p.key_length = 128/8
100
+ end
101
+
102
+ expected = "d1 da a7 86 15 f2 87 e6 a1 c8 b1 20 d7 06 2a 49"
103
+ p.hex_string.should == expected.gsub(' ','')
104
+
105
+ expected = "d1 da a7 86 15 f2 87 e6 a1 c8 b1 20 d7 06 2a 49" +
106
+ "3f 98 d2 03 e6 be 49 a6 ad f4 fa 57 4b 6e 64 ee"
107
+ p.key_length = 256/8
108
+ p.hex_string.should == expected.gsub(' ','')
109
+ end
110
+
111
+ it "should match the fifth test case in appendix B of RFC 3962" do
112
+ # Iteration count = 1200
113
+ # Pass phrase = (64 characters)
114
+ # "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
115
+ # Salt="pass phrase equals block size"
116
+ # 128-bit PBKDF2 output:
117
+ # 13 9c 30 c0 96 6b c3 2b a5 5f db f2 12 53 0a c9
118
+ # 256-bit PBKDF2 output:
119
+ # 13 9c 30 c0 96 6b c3 2b a5 5f db f2 12 53 0a c9
120
+ # c5 ec 59 f1 a4 52 f5 cc 9a d9 40 fe a0 59 8e d1
121
+ p = PBKDF2.new do |p|
122
+ p.hash_function = :sha1
123
+ p.iterations = 1200
124
+ p.password = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
125
+ p.salt = "pass phrase equals block size"
126
+ p.key_length = 128/8
127
+ end
128
+
129
+ expected = "13 9c 30 c0 96 6b c3 2b a5 5f db f2 12 53 0a c9"
130
+ p.hex_string.should == expected.gsub(' ','')
131
+
132
+ expected = "13 9c 30 c0 96 6b c3 2b a5 5f db f2 12 53 0a c9" +
133
+ "c5 ec 59 f1 a4 52 f5 cc 9a d9 40 fe a0 59 8e d1"
134
+ p.key_length = 256/8
135
+ p.hex_string.should == expected.gsub(' ','')
136
+ end
137
+
138
+ it "should match the sixth test case in appendix B of RFC 3962" do
139
+ # Iteration count = 1200
140
+ # Pass phrase = (65 characters)
141
+ # "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
142
+ # Salt = "pass phrase exceeds block size"
143
+ # 128-bit PBKDF2 output:
144
+ # 9c ca d6 d4 68 77 0c d5 1b 10 e6 a6 87 21 be 61
145
+ # 256-bit PBKDF2 output:
146
+ # 9c ca d6 d4 68 77 0c d5 1b 10 e6 a6 87 21 be 61
147
+ # 1a 8b 4d 28 26 01 db 3b 36 be 92 46 91 5e c8 2a
148
+ p = PBKDF2.new do |p|
149
+ p.hash_function = :sha1
150
+ p.iterations = 1200
151
+ p.password = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
152
+ p.salt = "pass phrase exceeds block size"
153
+ p.key_length = 128/8
154
+ end
155
+
156
+ expected = "9c ca d6 d4 68 77 0c d5 1b 10 e6 a6 87 21 be 61"
157
+ p.hex_string.should == expected.gsub(' ','')
158
+
159
+ expected = "9c ca d6 d4 68 77 0c d5 1b 10 e6 a6 87 21 be 61" +
160
+ "1a 8b 4d 28 26 01 db 3b 36 be 92 46 91 5e c8 2a"
161
+ p.key_length = 256/8
162
+ p.hex_string.should == expected.gsub(' ','')
163
+ end
164
+
165
+ it "should match the seventh test case in appendix B of RFC 3962" do
166
+ # Iteration count = 50
167
+ # Pass phrase = g-clef (0xf09d849e)
168
+ # Salt = "EXAMPLE.COMpianist"
169
+ # 128-bit PBKDF2 output:
170
+ # 6b 9c f2 6d 45 45 5a 43 a5 b8 bb 27 6a 40 3b 39
171
+ # 256-bit PBKDF2 output:
172
+ # 6b 9c f2 6d 45 45 5a 43 a5 b8 bb 27 6a 40 3b 39
173
+ # e7 fe 37 a0 c4 1e 02 c2 81 ff 30 69 e1 e9 4f 52
174
+ p = PBKDF2.new do |p|
175
+ p.hash_function = :sha1
176
+ p.iterations = 50
177
+ # this is a gorram horrible test case. it took me quite a while to
178
+ # track down why 0xf09d849e should be interpreted as "\360\235\204\236"
179
+ # (which is what other code uses for this example). the mysterious
180
+ # "g-clef" annotation didn't help (turns out to be a Unicode character
181
+ # in UTF8 -- ie, 0xf0 0x9d 0x84 0x9e)
182
+ p.password = [0xf09d849e].pack("N")
183
+ p.salt = "EXAMPLE.COMpianist"
184
+ p.key_length = 128/8
185
+ end
186
+
187
+ expected = "6b 9c f2 6d 45 45 5a 43 a5 b8 bb 27 6a 40 3b 39"
188
+ p.hex_string.should == expected.gsub(' ','')
189
+
190
+ expected = "6b 9c f2 6d 45 45 5a 43 a5 b8 bb 27 6a 40 3b 39" +
191
+ "e7 fe 37 a0 c4 1e 02 c2 81 ff 30 69 e1 e9 4f 52"
192
+ p.key_length = 256/8
193
+ p.hex_string.should == expected.gsub(' ','')
194
+ end
195
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,2 @@
1
+ Dir[File.join(File.dirname(__FILE__), '..', "lib", "*.rb")].each { |f| require f }
2
+
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pbkdf2-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sam Quigley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.2.9
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.2.9
30
+ - !ruby/object:Gem::Dependency
31
+ name: rdoc
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.4.2
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.4.2
46
+ description: This implementation conforms to RFC 2898, and has been tested using the
47
+ test vectors in Appendix B of RFC 3962. Note, however, that while those specifications
48
+ use HMAC-SHA-1, this implementation defaults to HMAC-SHA-256. (SHA-256 provides
49
+ a longer bit length. In addition, NIST has stated that SHA-1 should be phased out
50
+ due to concerns over recent cryptanalytic attacks.)
51
+ email: quigley@emerose.com
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files:
55
+ - LICENSE.TXT
56
+ - README.markdown
57
+ files:
58
+ - LICENSE.TXT
59
+ - README.markdown
60
+ - Rakefile
61
+ - VERSION
62
+ - benchmark.rb
63
+ - lib/pbkdf2.rb
64
+ - pbkdf2.gemspec
65
+ - spec/pbkdf2_spec.rb
66
+ - spec/rfc3962_spec.rb
67
+ - spec/spec.opts
68
+ - spec/spec_helper.rb
69
+ homepage: http://github.com/emerose/pbkdf2-ruby
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.23
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Password-Based Key Derivation Function 2 - PBKDF2
93
+ test_files: []