pbkdf2-ruby 0.2.0

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