|
| 1 | +require_relative '../../../spec_helper' |
| 2 | +require 'openssl' |
| 3 | + |
| 4 | +describe "OpenSSL::KDF.scrypt" do |
| 5 | + before :each do |
| 6 | + @defaults = { |
| 7 | + salt: "\x00".b * 16, |
| 8 | + N: 2**14, |
| 9 | + r: 8, |
| 10 | + p: 1, |
| 11 | + length: 32 |
| 12 | + } |
| 13 | + end |
| 14 | + |
| 15 | + it "creates the same value with the same input" do |
| 16 | + key = OpenSSL::KDF.scrypt("secret", **@defaults) |
| 17 | + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b |
| 18 | + end |
| 19 | + |
| 20 | + it "supports nullbytes embedded into the password" do |
| 21 | + key = OpenSSL::KDF.scrypt("sec\x00ret".b, **@defaults) |
| 22 | + key.should == "\xF9\xA4\xA0\xF1p\xF4\xF0\xCAT\xB4v\xEB\r7\x88N\xF7\x15]Ns\xFCwt4a\xC9\xC6\xA7\x13\x81&".b |
| 23 | + end |
| 24 | + |
| 25 | + it "coerces the password into a String using #to_str" do |
| 26 | + pass = mock("pass") |
| 27 | + pass.should_receive(:to_str).and_return("secret") |
| 28 | + key = OpenSSL::KDF.scrypt(pass, **@defaults) |
| 29 | + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b |
| 30 | + end |
| 31 | + |
| 32 | + it "coerces the salt into a String using #to_str" do |
| 33 | + salt = mock("salt") |
| 34 | + salt.should_receive(:to_str).and_return("\x00".b * 16) |
| 35 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, salt: salt) |
| 36 | + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b |
| 37 | + end |
| 38 | + |
| 39 | + it "coerces the N into an Integer using #to_int" do |
| 40 | + n = mock("N") |
| 41 | + n.should_receive(:to_int).and_return(2**14) |
| 42 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, N: n) |
| 43 | + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b |
| 44 | + end |
| 45 | + |
| 46 | + it "coerces the r into an Integer using #to_int" do |
| 47 | + r = mock("r") |
| 48 | + r.should_receive(:to_int).and_return(8) |
| 49 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, r: r) |
| 50 | + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b |
| 51 | + end |
| 52 | + |
| 53 | + it "coerces the p into an Integer using #to_int" do |
| 54 | + p = mock("p") |
| 55 | + p.should_receive(:to_int).and_return(1) |
| 56 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, p: p) |
| 57 | + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b |
| 58 | + end |
| 59 | + |
| 60 | + it "coerces the length into an Integer using #to_int" do |
| 61 | + length = mock("length") |
| 62 | + length.should_receive(:to_int).and_return(32) |
| 63 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: length) |
| 64 | + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b |
| 65 | + end |
| 66 | + |
| 67 | + it "accepts an empty password" do |
| 68 | + key = OpenSSL::KDF.scrypt("", **@defaults) |
| 69 | + key.should == "\xAA\xFC\xF5^E\x94v\xFFk\xE6\xF0vR\xE7\x13\xA7\xF5\x15'\x9A\xE4C\x9Dn\x18F_E\xD2\v\e\xB3".b |
| 70 | + end |
| 71 | + |
| 72 | + it "accepts an empty salt" do |
| 73 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, salt: "") |
| 74 | + key.should == "\x96\xACDl\xCB3/aN\xB0F\x8A#\xD7\x92\xD2O\x1E\v\xBB\xCE\xC0\xAA\xB9\x0F]\xB09\xEA8\xDD\e".b |
| 75 | + end |
| 76 | + |
| 77 | + it "accepts a zero length" do |
| 78 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: 0) |
| 79 | + key.should.empty? |
| 80 | + end |
| 81 | + |
| 82 | + it "accepts an arbitrary length" do |
| 83 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: 19) |
| 84 | + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D".b |
| 85 | + end |
| 86 | + |
| 87 | + it "raises a TypeError when password is not a String and does not respond to #to_str" do |
| 88 | + -> { |
| 89 | + OpenSSL::KDF.scrypt(Object.new, **@defaults) |
| 90 | + }.should raise_error(TypeError, "no implicit conversion of Object into String") |
| 91 | + end |
| 92 | + |
| 93 | + it "raises a TypeError when salt is not a String and does not respond to #to_str" do |
| 94 | + -> { |
| 95 | + OpenSSL::KDF.scrypt("secret", **@defaults, salt: Object.new) |
| 96 | + }.should raise_error(TypeError, "no implicit conversion of Object into String") |
| 97 | + end |
| 98 | + |
| 99 | + it "raises a TypeError when N is not an Integer and does not respond to #to_int" do |
| 100 | + -> { |
| 101 | + OpenSSL::KDF.scrypt("secret", **@defaults, N: Object.new) |
| 102 | + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") |
| 103 | + end |
| 104 | + |
| 105 | + it "raises a TypeError when r is not an Integer and does not respond to #to_int" do |
| 106 | + -> { |
| 107 | + OpenSSL::KDF.scrypt("secret", **@defaults, r: Object.new) |
| 108 | + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") |
| 109 | + end |
| 110 | + |
| 111 | + it "raises a TypeError when p is not an Integer and does not respond to #to_int" do |
| 112 | + -> { |
| 113 | + OpenSSL::KDF.scrypt("secret", **@defaults, p: Object.new) |
| 114 | + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") |
| 115 | + end |
| 116 | + |
| 117 | + it "raises a TypeError when length is not an Integer and does not respond to #to_int" do |
| 118 | + -> { |
| 119 | + OpenSSL::KDF.scrypt("secret", **@defaults, length: Object.new) |
| 120 | + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") |
| 121 | + end |
| 122 | + |
| 123 | + it "treats salt as a required keyword" do |
| 124 | + -> { |
| 125 | + OpenSSL::KDF.scrypt("secret", **@defaults.except(:salt)) |
| 126 | + }.should raise_error(ArgumentError, 'missing keyword: :salt') |
| 127 | + end |
| 128 | + |
| 129 | + it "treats N as a required keyword" do |
| 130 | + -> { |
| 131 | + OpenSSL::KDF.scrypt("secret", **@defaults.except(:N)) |
| 132 | + }.should raise_error(ArgumentError, 'missing keyword: :N') |
| 133 | + end |
| 134 | + |
| 135 | + it "treats r as a required keyword" do |
| 136 | + -> { |
| 137 | + OpenSSL::KDF.scrypt("secret", **@defaults.except(:r)) |
| 138 | + }.should raise_error(ArgumentError, 'missing keyword: :r') |
| 139 | + end |
| 140 | + |
| 141 | + it "treats p as a required keyword" do |
| 142 | + -> { |
| 143 | + OpenSSL::KDF.scrypt("secret", **@defaults.except(:p)) |
| 144 | + }.should raise_error(ArgumentError, 'missing keyword: :p') |
| 145 | + end |
| 146 | + |
| 147 | + it "treats length as a required keyword" do |
| 148 | + -> { |
| 149 | + OpenSSL::KDF.scrypt("secret", **@defaults.except(:length)) |
| 150 | + }.should raise_error(ArgumentError, 'missing keyword: :length') |
| 151 | + end |
| 152 | + |
| 153 | + it "treats all keywords as required" do |
| 154 | + -> { |
| 155 | + OpenSSL::KDF.scrypt("secret") |
| 156 | + }.should raise_error(ArgumentError, 'missing keywords: :salt, :N, :r, :p, :length') |
| 157 | + end |
| 158 | + |
| 159 | + it "requires N to be a power of 2" do |
| 160 | + -> { |
| 161 | + OpenSSL::KDF.scrypt("secret", **@defaults, N: 2**14 - 1) |
| 162 | + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) |
| 163 | + end |
| 164 | + |
| 165 | + it "requires N to be at least 2" do |
| 166 | + key = OpenSSL::KDF.scrypt("secret", **@defaults, N: 2) |
| 167 | + key.should == "\x06A$a\xA9!\xBE\x01\x85\xA7\x18\xBCEa\x82\xC5\xFEl\x93\xAB\xBD\xF7\x8B\x84\v\xFC\eN\xEBQ\xE6\xD2".b |
| 168 | + |
| 169 | + -> { |
| 170 | + OpenSSL::KDF.scrypt("secret", **@defaults, N: 1) |
| 171 | + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) |
| 172 | + |
| 173 | + -> { |
| 174 | + OpenSSL::KDF.scrypt("secret", **@defaults, N: 0) |
| 175 | + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) |
| 176 | + |
| 177 | + -> { |
| 178 | + OpenSSL::KDF.scrypt("secret", **@defaults, N: -1) |
| 179 | + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) |
| 180 | + end |
| 181 | + |
| 182 | + it "requires r to be positive" do |
| 183 | + -> { |
| 184 | + OpenSSL::KDF.scrypt("secret", **@defaults, r: 0) |
| 185 | + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) |
| 186 | + |
| 187 | + -> { |
| 188 | + OpenSSL::KDF.scrypt("secret", **@defaults, r: -1) |
| 189 | + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) |
| 190 | + end |
| 191 | + |
| 192 | + it "requires p to be positive" do |
| 193 | + -> { |
| 194 | + OpenSSL::KDF.scrypt("secret", **@defaults, p: 0) |
| 195 | + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) |
| 196 | + |
| 197 | + -> { |
| 198 | + OpenSSL::KDF.scrypt("secret", **@defaults, p: -1) |
| 199 | + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) |
| 200 | + end |
| 201 | + |
| 202 | + it "requires length to be not negative" do |
| 203 | + -> { |
| 204 | + OpenSSL::KDF.scrypt("secret", **@defaults, length: -1) |
| 205 | + }.should raise_error(ArgumentError, "negative string size (or size too big)") |
| 206 | + end |
| 207 | +end |
0 commit comments