Skip to content

Commit 0c8e2d1

Browse files
authored
Merge pull request #1092 from herwinw/openssl_kdf_pbkdf2_hmac
Add specs for OpenSSL::KDF.pbkdf2_hmac
2 parents 5754dde + 2516f65 commit 0c8e2d1

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
require_relative '../../../spec_helper'
2+
require 'openssl'
3+
4+
describe "OpenSSL::KDF.pbkdf2_hmac" do
5+
before :each do
6+
@defaults = {
7+
salt: "\x00".b * 16,
8+
iterations: 20_000,
9+
length: 16,
10+
hash: "sha1"
11+
}
12+
end
13+
14+
it "creates the same value with the same input" do
15+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults)
16+
key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b
17+
end
18+
19+
it "supports nullbytes embedded in the password" do
20+
key = OpenSSL::KDF.pbkdf2_hmac("sec\x00ret".b, **@defaults)
21+
key.should == "\xB9\x7F\xB0\xC2\th\xC8<\x86\xF3\x94Ij7\xEF\xF1".b
22+
end
23+
24+
it "coerces the password into a String using #to_str" do
25+
pass = mock("pass")
26+
pass.should_receive(:to_str).and_return("secret")
27+
key = OpenSSL::KDF.pbkdf2_hmac(pass, **@defaults)
28+
key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b
29+
end
30+
31+
it "coerces the salt into a String using #to_str" do
32+
salt = mock("salt")
33+
salt.should_receive(:to_str).and_return("\x00".b * 16)
34+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: salt)
35+
key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b
36+
end
37+
38+
it "coerces the iterations into an Integer using #to_int" do
39+
iterations = mock("iterations")
40+
iterations.should_receive(:to_int).and_return(20_000)
41+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: iterations)
42+
key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b
43+
end
44+
45+
it "coerces the length into an Integer using #to_int" do
46+
length = mock("length")
47+
length.should_receive(:to_int).and_return(16)
48+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: length)
49+
key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b
50+
end
51+
52+
it "accepts a OpenSSL::Digest object as hash" do
53+
hash = OpenSSL::Digest.new("sha1")
54+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: hash)
55+
key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b
56+
end
57+
58+
it "accepts an empty password" do
59+
key = OpenSSL::KDF.pbkdf2_hmac("", **@defaults)
60+
key.should == "k\x9F-\xB1\xF7\x9A\v\xA1(C\xF9\x85!P\xEF\x8C".b
61+
end
62+
63+
it "accepts an empty salt" do
64+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: "")
65+
key.should == "\xD5f\xE5\xEA\xF91\x1D\xD3evD\xED\xDB\xE80\x80".b
66+
end
67+
68+
it "accepts an empty length" do
69+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: 0)
70+
key.should.empty?
71+
end
72+
73+
it "accepts an arbitrary length" do
74+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: 19)
75+
key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0\xCF\xBB\x7F".b
76+
end
77+
78+
it "accepts any hash function known to OpenSSL" do
79+
key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "sha512")
80+
key.should == "N\x12}D\xCE\x99\xDBC\x8E\xEC\xAAr\xEA1\xDF\xFF".b
81+
end
82+
83+
it "raises a TypeError when password is not a String and does not respond to #to_str" do
84+
-> {
85+
OpenSSL::KDF.pbkdf2_hmac(Object.new, **@defaults)
86+
}.should raise_error(TypeError, "no implicit conversion of Object into String")
87+
end
88+
89+
it "raises a TypeError when salt is not a String and does not respond to #to_str" do
90+
-> {
91+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: Object.new)
92+
}.should raise_error(TypeError, "no implicit conversion of Object into String")
93+
end
94+
95+
it "raises a TypeError when iterations is not an Integer and does not respond to #to_int" do
96+
-> {
97+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: Object.new)
98+
}.should raise_error(TypeError, "no implicit conversion of Object into Integer")
99+
end
100+
101+
it "raises a TypeError when length is not an Integer and does not respond to #to_int" do
102+
-> {
103+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: Object.new)
104+
}.should raise_error(TypeError, "no implicit conversion of Object into Integer")
105+
end
106+
107+
it "raises a TypeError when hash is neither a String nor an OpenSSL::Digest" do
108+
-> {
109+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: Object.new)
110+
}.should raise_error(TypeError, "wrong argument type Object (expected OpenSSL/Digest)")
111+
end
112+
113+
it "raises a TypeError when hash is neither a String nor an OpenSSL::Digest, it does not try to call #to_str" do
114+
hash = mock("hash")
115+
hash.should_not_receive(:to_str)
116+
-> {
117+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: hash)
118+
}.should raise_error(TypeError, "wrong argument type MockObject (expected OpenSSL/Digest)")
119+
end
120+
121+
it "raises a RuntimeError for unknown digest algorithms" do
122+
-> {
123+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "wd40")
124+
}.should raise_error(RuntimeError, /Unsupported digest algorithm \(wd40\)/)
125+
end
126+
127+
it "treats salt as a required keyword" do
128+
-> {
129+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:salt))
130+
}.should raise_error(ArgumentError, 'missing keyword: :salt')
131+
end
132+
133+
it "treats iterations as a required keyword" do
134+
-> {
135+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:iterations))
136+
}.should raise_error(ArgumentError, 'missing keyword: :iterations')
137+
end
138+
139+
it "treats length as a required keyword" do
140+
-> {
141+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:length))
142+
}.should raise_error(ArgumentError, 'missing keyword: :length')
143+
end
144+
145+
it "treats hash as a required keyword" do
146+
-> {
147+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:hash))
148+
}.should raise_error(ArgumentError, 'missing keyword: :hash')
149+
end
150+
151+
it "treats all keywords as required" do
152+
-> {
153+
OpenSSL::KDF.pbkdf2_hmac("secret")
154+
}.should raise_error(ArgumentError, 'missing keywords: :salt, :iterations, :length, :hash')
155+
end
156+
157+
158+
guard -> { OpenSSL::OPENSSL_VERSION_NUMBER < 0x30000000 } do
159+
it "treats 0 or less iterations as a single iteration" do
160+
salt = "\x00".b * 16
161+
length = 16
162+
hash = "sha1"
163+
164+
# "Any iter less than 1 is treated as a single iteration."
165+
key0 = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: 0)
166+
key_negative = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: -1)
167+
key1 = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: 1)
168+
key0.should == key1
169+
key_negative.should == key1
170+
end
171+
end
172+
173+
guard -> { OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 } do
174+
it "raises an OpenSSL::KDF::KDFError for 0 or less iterations" do
175+
-> {
176+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: 0)
177+
}.should raise_error(OpenSSL::KDF::KDFError, "PKCS5_PBKDF2_HMAC: invalid iteration count")
178+
179+
-> {
180+
OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: -1)
181+
}.should raise_error(OpenSSL::KDF::KDFError, /PKCS5_PBKDF2_HMAC/)
182+
end
183+
end
184+
end

0 commit comments

Comments
 (0)