enzoic 1.1.3 → 1.3.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.
- checksums.yaml +5 -5
- data/README.md +11 -1
- data/Rakefile +4 -4
- data/enzoic.gemspec +2 -2
- data/ext/argon2-wrapper/libargon2-wrapper.bundle +0 -0
- data/ext/phc-winner-argon2/argon2 +0 -0
- data/ext/phc-winner-argon2/argon2.dSYM/Contents/Resources/DWARF/argon2 +0 -0
- data/ext/phc-winner-argon2/libargon2.0.dylib +0 -0
- data/ext/phc-winner-argon2/libargon2.0.dylib.dSYM/Contents/Resources/DWARF/libargon2.0.dylib +0 -0
- data/ext/phc-winner-argon2/libargon2.a +0 -0
- data/ext/phc-winner-argon2/src/argon2.o +0 -0
- data/ext/phc-winner-argon2/src/blake2/blake2b.o +0 -0
- data/ext/phc-winner-argon2/src/core.o +0 -0
- data/ext/phc-winner-argon2/src/encoding.o +0 -0
- data/ext/phc-winner-argon2/src/ref.o +0 -0
- data/ext/phc-winner-argon2/src/thread.o +0 -0
- data/lib/enzoic/hashing.rb +173 -8
- data/lib/enzoic/password_type.rb +27 -0
- data/lib/enzoic/version.rb +1 -1
- data/lib/enzoic.rb +222 -98
- metadata +10 -11
- data/ext/phc-winner-argon2/src/opt.o +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a9299615755ffc582bb35414b6851ba06c11f236565542ef1d7b5d8b681ec776
|
4
|
+
data.tar.gz: 12e91e3208c526598672ecbf066af5734237345954e76320ae7163691d4f25e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 888318919c30799a05c6cb91ddf0d986a5b86ac9d0a4ca0db76ed725fac7f4e91dd2d8a9a9353af0bd375b9c3e4284a979a3391d1649292521a145b04f9e577f
|
7
|
+
data.tar.gz: dad8311f61c0bdadd39436e53db8ec02e0f61313233aba3f0337c7cf403ec3cbe0b04d2b97a16194c3c4d1c2e578cdf0233bfea560046208f0c692e08a73497b
|
data/README.md
CHANGED
@@ -35,6 +35,7 @@ require 'enzoic'
|
|
35
35
|
enzoic = Enzoic::Enzoic.new(apiKey: YOUR_API_KEY, secret: YOUR_API_SECRET)
|
36
36
|
|
37
37
|
# Check whether a password has been compromised
|
38
|
+
# see https://www.enzoic.com/docs-passwords-api/ for more information
|
38
39
|
if enzoic.check_password("password-to-test")
|
39
40
|
puts("Password is compromised")
|
40
41
|
else
|
@@ -42,6 +43,7 @@ else
|
|
42
43
|
end
|
43
44
|
|
44
45
|
# Check whether a specific set of credentials are compromised
|
46
|
+
# see https://www.enzoic.com/docs-credentials-api/ for more information
|
45
47
|
if enzoic.check_credentials("test@enzoic.com", "password-to-test")
|
46
48
|
puts("Credentials are compromised")
|
47
49
|
else
|
@@ -53,7 +55,7 @@ end
|
|
53
55
|
# lastCheckDate is the timestamp for the last check you performed for this user.
|
54
56
|
# If the DateTime you provide for the last check is greater than the timestamp Enzoic has
|
55
57
|
# for the last breach affecting this user, the check will not be performed.
|
56
|
-
# This can be used to substantially increase performance.
|
58
|
+
# This can be used to substantially increase performance.
|
57
59
|
if enzoic.check_credentials("test@enzoic.com", "password-to-test", DateTime.parse("2019-07-15T19:57:43.000Z"))
|
58
60
|
puts("Credentials are compromised")
|
59
61
|
else
|
@@ -61,12 +63,20 @@ else
|
|
61
63
|
end
|
62
64
|
|
63
65
|
# get all exposures for a given user
|
66
|
+
# see https://www.enzoic.com/docs-exposures-api/#get-exposures for more information
|
64
67
|
exposures = enzoic.get_exposures_for_user("test@enzoic.com")
|
65
68
|
puts(exposures.count.to_s + " exposures found for test@enzoic.com")
|
66
69
|
|
67
70
|
# now get the full details for the first exposure found
|
71
|
+
# see https://www.enzoic.com/docs-exposures-api/#get-exposure-details for more information
|
68
72
|
details = enzoic.get_exposure_details(exposures.exposures[0])
|
69
73
|
puts("First exposure for test@enzoic.com was " + details.title)
|
74
|
+
|
75
|
+
# get all passwords for a given user - requires special approval, contact Enzoic sales
|
76
|
+
# see https://www.enzoic.com/docs-raw-passwords-api/ for more information
|
77
|
+
user_passwords = enzoic.get_passwords_for_user("eicar_0@enzoic.com")
|
78
|
+
puts("First password for eicar_0@enzoic.com was " + user_passwords.passwords[0].password)
|
79
|
+
|
70
80
|
```
|
71
81
|
|
72
82
|
More information in reference format can be found below.
|
data/Rakefile
CHANGED
@@ -20,9 +20,9 @@ Rake::ExtensionTask.new('whirlpool', gemspec) do |ext|
|
|
20
20
|
ext.lib_dir = 'lib/digest'
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
Rake::ExtensionTask.new('argon2-wrapper', gemspec) do |ext|
|
24
|
+
ext.ext_dir = 'ext/argon2-wrapper'
|
25
|
+
ext.lib_dir = 'lib/enzoic'
|
26
|
+
end
|
27
27
|
|
28
28
|
task :default => :test
|
data/enzoic.gemspec
CHANGED
@@ -20,14 +20,14 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.bindir = "exe"
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
22
|
spec.require_paths = ["lib"]
|
23
|
-
spec.add_dependency 'ffi', '~> 1.
|
23
|
+
spec.add_dependency 'ffi', '~> 1.15.5'
|
24
24
|
spec.add_dependency 'ffi-compiler', '~> 1.0.1'
|
25
25
|
spec.add_dependency 'rest-client', '~> 2.0', '>= 2.0.2'
|
26
26
|
spec.add_dependency 'bcrypt', '~> 3.1', '>= 3.1.11'
|
27
27
|
spec.add_dependency 'unix-crypt', '~> 1.3'
|
28
28
|
spec.add_dependency 'base64url', '~> 1.0', '>= 1.0.1'
|
29
29
|
|
30
|
-
spec.add_development_dependency "bundler", '~> 2.
|
30
|
+
spec.add_development_dependency "bundler", '~> 2.2.11', '>= 2.2.11'
|
31
31
|
spec.add_development_dependency "rake", '~> 10.4', '>= 10.4.2'
|
32
32
|
spec.add_development_dependency "test-unit", '~> 3.2', '>= 3.2.4'
|
33
33
|
spec.add_development_dependency "rake-compiler", '~> 1.0', '>= 1.0.4'
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/ext/phc-winner-argon2/libargon2.0.dylib.dSYM/Contents/Resources/DWARF/libargon2.0.dylib
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/enzoic/hashing.rb
CHANGED
@@ -3,7 +3,8 @@ require 'digest'
|
|
3
3
|
require 'bcrypt'
|
4
4
|
require 'unix_crypt'
|
5
5
|
require 'zlib'
|
6
|
-
require 'digest/whirlpool'
|
6
|
+
require 'digest/whirlpool.bundle'
|
7
|
+
#require 'open_ssl'
|
7
8
|
require 'base64url'
|
8
9
|
|
9
10
|
module Enzoic
|
@@ -24,6 +25,14 @@ module Enzoic
|
|
24
25
|
return Digest::SHA1.hexdigest to_hash
|
25
26
|
end
|
26
27
|
|
28
|
+
def self.sha1_binary(to_hash)
|
29
|
+
return Digest::SHA1.digest(to_hash).bytes
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.sha1_binary_array(to_hash_bytes)
|
33
|
+
return Digest::SHA1.digest(to_hash_bytes.pack('c*')).bytes
|
34
|
+
end
|
35
|
+
|
27
36
|
def self.sha256(to_hash)
|
28
37
|
return Digest::SHA256.hexdigest to_hash
|
29
38
|
end
|
@@ -91,7 +100,7 @@ module Enzoic
|
|
91
100
|
|
92
101
|
itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
93
102
|
to_hash_bytes = to_hash.bytes
|
94
|
-
count = 2**itoa64.index(salt[3])
|
103
|
+
count = 2 ** itoa64.index(salt[3])
|
95
104
|
justsalt = salt[4..12]
|
96
105
|
|
97
106
|
hash = self.md5_binary(justsalt + to_hash)
|
@@ -148,13 +157,169 @@ module Enzoic
|
|
148
157
|
end
|
149
158
|
|
150
159
|
def self.md5crypt(to_hash, salt)
|
151
|
-
return UnixCrypt::MD5.build(to_hash, salt.start_with?("$1$") ? salt[3..salt.length] : salt)
|
160
|
+
return UnixCrypt::MD5.build(to_hash, salt.start_with?("$1$") ? salt[3..salt.length] : salt)
|
152
161
|
end
|
153
162
|
|
154
163
|
def self.custom_algorithm4(to_hash, salt)
|
155
164
|
return self.bcrypt(self.md5(to_hash), salt)
|
156
165
|
end
|
157
166
|
|
167
|
+
def self.custom_algorithm5(to_hash, salt)
|
168
|
+
return self.sha256(self.md5(to_hash + salt))
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.osCommerce_AEF(to_hash, salt)
|
172
|
+
return self.md5(salt + to_hash)
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.desCrypt(to_hash, salt)
|
176
|
+
return UnixCrypt::DES.build(to_hash, salt)
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.convertToUnsigned(val)
|
180
|
+
return [val].pack('L').unpack('L').first
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.mySQLPre4_1(to_hash)
|
184
|
+
nr = 1345345333
|
185
|
+
add = 7
|
186
|
+
nr2 = 0x12345671
|
187
|
+
|
188
|
+
for i in 0..to_hash.length - 1 do
|
189
|
+
c = to_hash[i]
|
190
|
+
|
191
|
+
if c == " " || c == "\t"
|
192
|
+
next
|
193
|
+
end
|
194
|
+
|
195
|
+
tmp = c.ord
|
196
|
+
nr = nr ^ ((((nr & 63) + add) * tmp) + (self.convertToUnsigned(nr << 8)))
|
197
|
+
nr2 += (self.convertToUnsigned(nr2 << 8)) ^ nr
|
198
|
+
add += tmp
|
199
|
+
end
|
200
|
+
|
201
|
+
result1 = nr & ((self.convertToUnsigned(1 << 31)) - 1)
|
202
|
+
result2 = nr2 & ((self.convertToUnsigned(1 << 31)) - 1)
|
203
|
+
|
204
|
+
return result1.to_s(16) + result2.to_s(16)
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.mySQLPost4_1(to_hash)
|
208
|
+
return "*" + self.bytes_to_hex(self.sha1_binary_array(self.sha1_binary(to_hash)));
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.punBB(to_hash, salt)
|
212
|
+
return self.sha1(salt + self.sha1(to_hash))
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.custom_algorithm6(to_hash, salt)
|
216
|
+
return self.sha1(to_hash + salt)
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.partial_md5_20(to_hash)
|
220
|
+
return self.md5(to_hash)[0..19]
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.partial_md5_29(to_hash)
|
224
|
+
return self.md5(to_hash)[0..28]
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.ave_datalife_diferior(to_hash)
|
228
|
+
return self.md5(self.md5(to_hash))
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.django_md5(to_hash, salt)
|
232
|
+
return "md5$" + salt + "$" + self.md5(salt + to_hash)
|
233
|
+
end
|
234
|
+
|
235
|
+
def self.django_sha1(to_hash, salt)
|
236
|
+
return "sha1$" + salt + "$" + self.sha1(salt + to_hash)
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.pligg_cms(to_hash, salt)
|
240
|
+
return salt + self.sha1(salt + to_hash)
|
241
|
+
end
|
242
|
+
|
243
|
+
def self.runcms_smf1_1(to_hash, salt)
|
244
|
+
return self.sha1(salt + to_hash)
|
245
|
+
end
|
246
|
+
|
247
|
+
def self.ntlm(to_hash)
|
248
|
+
pwd = to_hash.dup
|
249
|
+
pwd = pwd.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
|
250
|
+
OpenSSL::Digest::MD4.hexdigest pwd
|
251
|
+
end
|
252
|
+
|
253
|
+
def self.sha1dash(to_hash, salt)
|
254
|
+
return self.sha1("--" + salt + "--" + to_hash + "--")
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.sha384(to_hash)
|
258
|
+
return Digest::SHA384.hexdigest to_hash
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.custom_algorithm7(to_hash, salt)
|
262
|
+
derived_salt = self.sha1(salt)
|
263
|
+
return OpenSSL::HMAC.hexdigest("SHA256",
|
264
|
+
"d2e1a4c569e7018cc142e9cce755a964bd9b193d2d31f02d80bb589c959afd7e",
|
265
|
+
derived_salt + to_hash)
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.custom_algorithm9(to_hash, salt)
|
269
|
+
result = self.sha512(to_hash + salt)
|
270
|
+
for i in 0..10 do
|
271
|
+
result = self.sha512(result)
|
272
|
+
end
|
273
|
+
return result
|
274
|
+
end
|
275
|
+
|
276
|
+
def self.sha256crypt(to_hash, salt)
|
277
|
+
return self.sha_crypt("5", UnixCrypt::SHA256, to_hash, salt)
|
278
|
+
end
|
279
|
+
|
280
|
+
def self.sha512crypt(to_hash, salt)
|
281
|
+
return self.sha_crypt("6", UnixCrypt::SHA512, to_hash, salt)
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.sha_crypt(crypt_version, crypter, to_hash, salt)
|
285
|
+
# special handling if the salt contains an embedded rounds specifier
|
286
|
+
if salt.start_with?("$" + crypt_version + "$") && salt.include?("$rounds=")
|
287
|
+
# extract rounds
|
288
|
+
rounds_starting_idx = salt.index("$rounds=") + 8
|
289
|
+
rounds = salt[rounds_starting_idx..salt.length]
|
290
|
+
salt_portion = rounds[rounds.index("$") + 1..rounds.length]
|
291
|
+
|
292
|
+
begin
|
293
|
+
rounds = Integer(rounds[0..rounds.index("$") - 1])
|
294
|
+
rescue ArgumentError
|
295
|
+
rounds = 5000
|
296
|
+
end
|
297
|
+
|
298
|
+
result = crypter.build(to_hash, salt_portion, rounds)
|
299
|
+
|
300
|
+
# if the default rounds of 5000 was used, add this back in to the resultant hash as this library, unlike most,
|
301
|
+
# will strip it out.
|
302
|
+
if rounds == 5000
|
303
|
+
result = result[0..2] + "rounds=5000$" + result[3..result.length]
|
304
|
+
end
|
305
|
+
|
306
|
+
return result
|
307
|
+
end
|
308
|
+
return crypter.build(to_hash, salt.start_with?("$" + crypt_version + "$") ? salt[3..salt.length] : salt)
|
309
|
+
end
|
310
|
+
|
311
|
+
def self.custom_algorithm10(to_hash, salt)
|
312
|
+
return self.sha512(to_hash + ":" + salt)
|
313
|
+
end
|
314
|
+
|
315
|
+
def self.hmac_sha1_salt_as_hash(to_hash, salt)
|
316
|
+
return OpenSSL::HMAC.hexdigest("sha1", salt, to_hash)
|
317
|
+
end
|
318
|
+
|
319
|
+
def self.authMeSHA256(to_hash, salt)
|
320
|
+
return "$SHA$" + salt + "$" + self.sha256(self.sha256(to_hash) + salt);
|
321
|
+
end
|
322
|
+
|
158
323
|
def self.argon2_raw(to_hash, salt)
|
159
324
|
time_cost = 3
|
160
325
|
mem_cost = 10
|
@@ -238,13 +403,13 @@ module Enzoic
|
|
238
403
|
end
|
239
404
|
|
240
405
|
def self.xor(byte_array1, byte_array2)
|
241
|
-
|
406
|
+
result = Array.new(byte_array1.length);
|
242
407
|
|
243
|
-
|
244
|
-
|
245
|
-
|
408
|
+
for i in 0..byte_array1.length - 1 do
|
409
|
+
result[i] = byte_array1[i] ^ byte_array2[i];
|
410
|
+
end
|
246
411
|
|
247
|
-
|
412
|
+
return result;
|
248
413
|
end
|
249
414
|
|
250
415
|
def self.bytes_to_hex(bytes)
|
data/lib/enzoic/password_type.rb
CHANGED
@@ -18,6 +18,33 @@ module Enzoic
|
|
18
18
|
SHA512 = 14
|
19
19
|
MD5Crypt = 16
|
20
20
|
CustomAlgorithm4 = 17
|
21
|
+
CustomAlgorithm5 = 18
|
22
|
+
OsCommerce_AEF = 19
|
23
|
+
DESCrypt = 20
|
24
|
+
MySQLPre4_1 = 21
|
25
|
+
MySQLPost4_1 = 22
|
26
|
+
PeopleSoft = 23
|
27
|
+
PunBB = 24
|
28
|
+
CustomAlgorithm6 = 25
|
29
|
+
PartialMD5_20 = 26
|
30
|
+
AVE_DataLife_Diferior = 27
|
31
|
+
DjangoMD5 = 28
|
32
|
+
DjangoSHA1 = 29
|
33
|
+
PartialMD5_29 = 30
|
34
|
+
PliggCMS = 31
|
35
|
+
RunCMS_SMF1_1 = 32
|
36
|
+
NTLM = 33
|
37
|
+
SHA1Dash = 34
|
38
|
+
SHA384 = 35
|
39
|
+
CustomAlgorithm7 = 36
|
40
|
+
CustomAlgorithm8 = 37
|
41
|
+
CustomAlgorithm9 = 38
|
42
|
+
SHA512Crypt = 39
|
43
|
+
CustomAlgorithm10 = 40
|
44
|
+
HMACSHA1_SaltAsHash = 41
|
45
|
+
AuthMeSHA256 = 42
|
46
|
+
SHA256Crypt = 43
|
47
|
+
|
21
48
|
Unknown = 97
|
22
49
|
UnusablePassword = 98
|
23
50
|
None = 99
|
data/lib/enzoic/version.rb
CHANGED
data/lib/enzoic.rb
CHANGED
@@ -15,61 +15,72 @@ module Enzoic
|
|
15
15
|
# to access the Enzoic API.
|
16
16
|
class Enzoic
|
17
17
|
def initialize(options = {})
|
18
|
-
@apiKey = options[:apiKey] || ''
|
18
|
+
@apiKey = options[:apiKey] || ''
|
19
19
|
raise EnzoicFail, "No API key provided" if @apiKey == ''
|
20
|
-
@secret = options[:secret] || ''
|
20
|
+
@secret = options[:secret] || ''
|
21
21
|
raise EnzoicFail, "No Secret provided" if @secret == ''
|
22
22
|
@baseURL = options[:baseURL] || "https://api.enzoic.com/v1"
|
23
23
|
@authString = calc_auth_string(@apiKey, @secret)
|
24
24
|
end
|
25
25
|
|
26
|
-
def check_credentials(username, password,
|
26
|
+
def check_credentials(username, password, last_check_timestamp = Date.new(1980, 1, 1))
|
27
27
|
raise EnzoicFail, "API key/Secret not set" if !@authString || @authString == ''
|
28
28
|
|
29
|
-
response = make_rest_call(@baseURL + Constants::ACCOUNTS_API_PATH +
|
29
|
+
response = make_rest_call(@baseURL + Constants::ACCOUNTS_API_PATH +
|
30
|
+
"?username=" + Hashing.sha256(username.downcase),
|
31
|
+
"GET", nil)
|
30
32
|
|
31
|
-
if
|
33
|
+
if response == "404"
|
32
34
|
return false
|
33
35
|
end
|
34
36
|
|
35
37
|
account_response = JSON.parse(response)
|
36
38
|
|
37
39
|
# if lastCheckTimestamp was provided, see if we need to go any further
|
38
|
-
if
|
40
|
+
if Date.parse(account_response["lastBreachDate"]) > last_check_timestamp
|
39
41
|
hashes_required = account_response["passwordHashesRequired"]
|
40
42
|
|
41
43
|
bcrypt_count = 0
|
42
44
|
query_string = ""
|
45
|
+
credential_hashes = []
|
43
46
|
|
44
|
-
|
45
|
-
hash_spec = hashes_required[i]
|
46
|
-
|
47
|
+
hashes_required.each do |hash_spec|
|
47
48
|
# bcrypt gets far too expensive for good response time if there are many of them to calculate.
|
48
49
|
# some mostly garbage accounts have accumulated a number of them in our DB and if we happen to hit one it
|
49
50
|
# kills performance, so short circuit out after at most 2 BCrypt hashes
|
50
|
-
if
|
51
|
-
if
|
51
|
+
if hash_spec["hashType"] != PasswordType::BCrypt || bcrypt_count <= 2
|
52
|
+
if hash_spec["hashType"] == PasswordType::BCrypt
|
52
53
|
bcrypt_count = bcrypt_count + 1
|
53
54
|
end
|
54
55
|
|
55
|
-
if
|
56
|
-
credential_hash = calc_credential_hash(username, password, account_response["salt"], hash_spec)
|
56
|
+
if hash_spec["hashType"] != nil
|
57
|
+
credential_hash = calc_credential_hash(username.downcase, password, account_response["salt"], hash_spec)
|
58
|
+
|
59
|
+
if credential_hash != nil
|
60
|
+
credential_hashes << credential_hash
|
57
61
|
|
58
|
-
|
59
|
-
|
60
|
-
query_string = query_string + "?hashes=" + CGI.escape(credential_hash);
|
62
|
+
if query_string.length == 0
|
63
|
+
query_string = query_string + "?partialHashes=" + CGI.escape(credential_hash[0..6])
|
61
64
|
else
|
62
|
-
query_string = query_string + "&
|
65
|
+
query_string = query_string + "&partialHashes=" + CGI.escape(credential_hash[0..6])
|
63
66
|
end
|
64
67
|
end
|
65
68
|
end
|
66
69
|
end
|
67
70
|
end
|
68
71
|
|
69
|
-
if
|
72
|
+
if query_string.length > 0
|
70
73
|
creds_response = make_rest_call(
|
71
|
-
|
72
|
-
|
74
|
+
@baseURL + Constants::CREDENTIALS_API_PATH + query_string, "GET", nil)
|
75
|
+
|
76
|
+
if creds_response != "404"
|
77
|
+
creds_result = JSON.parse(creds_response, object_class: OpenStruct)
|
78
|
+
creds_result.candidateHashes.each do |candidateHash|
|
79
|
+
if credential_hashes.include? candidateHash
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
73
84
|
end
|
74
85
|
end
|
75
86
|
|
@@ -77,21 +88,36 @@ module Enzoic
|
|
77
88
|
end
|
78
89
|
|
79
90
|
def check_password(password)
|
91
|
+
md5 = Hashing.md5(password)
|
92
|
+
sha1 = Hashing.sha1(password)
|
93
|
+
sha256 = Hashing.sha256(password)
|
94
|
+
|
80
95
|
response = make_rest_call(
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
96
|
+
@baseURL + Constants::PASSWORDS_API_PATH, "POST",
|
97
|
+
'{' +
|
98
|
+
'"partialMD5":"' + md5[0..6] + '",' +
|
99
|
+
'"partialSHA1":"' + sha1[0..6] + '",' +
|
100
|
+
'"partialSHA256":"' + sha256[0..6] + '"' +
|
101
|
+
'}')
|
86
102
|
|
87
|
-
|
103
|
+
if response != "404"
|
104
|
+
result = JSON.parse(response, object_class: OpenStruct)
|
105
|
+
|
106
|
+
result.candidates.each do |candidate|
|
107
|
+
if candidate.md5 == md5 || candidate.sha1 == sha1 || candidate.sha256 == sha256
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
return false
|
88
114
|
end
|
89
115
|
|
90
116
|
def get_exposures_for_user(username)
|
91
|
-
response = make_rest_call(@baseURL + Constants::EXPOSURES_API_PATH + "?username=" + Hashing.sha256(username),
|
92
|
-
|
117
|
+
response = make_rest_call(@baseURL + Constants::EXPOSURES_API_PATH + "?username=" + Hashing.sha256(username.downcase),
|
118
|
+
"GET", nil)
|
93
119
|
|
94
|
-
if
|
120
|
+
if response == "404"
|
95
121
|
# don't have this email in the DB - return empty response
|
96
122
|
return JSON.parse('{ "count": 0, "exposures": [] }', object_class: OpenStruct)
|
97
123
|
else
|
@@ -102,9 +128,9 @@ module Enzoic
|
|
102
128
|
|
103
129
|
def get_exposure_details(exposure_id)
|
104
130
|
response = make_rest_call(@baseURL + Constants::EXPOSURES_API_PATH + "?id=" + CGI.escape(exposure_id),
|
105
|
-
|
131
|
+
"GET", nil)
|
106
132
|
|
107
|
-
if
|
133
|
+
if response != "404"
|
108
134
|
# deserialize response
|
109
135
|
return JSON.parse(response, object_class: OpenStruct)
|
110
136
|
else
|
@@ -112,82 +138,180 @@ module Enzoic
|
|
112
138
|
end
|
113
139
|
end
|
114
140
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
141
|
+
def get_passwords_for_user(username)
|
142
|
+
response = make_rest_call(@baseURL + Constants::ACCOUNTS_API_PATH + "?username=" +
|
143
|
+
Hashing.sha256(username.downcase) + "&includePasswords=1",
|
144
|
+
"GET", nil)
|
145
|
+
|
146
|
+
if response == "404"
|
147
|
+
# don't have this email in the DB - return empty response
|
148
|
+
return JSON.parse('{ "lastBreachDate": null, "passwords": [] }', object_class: OpenStruct)
|
149
|
+
else
|
150
|
+
# deserialize response
|
151
|
+
return JSON.parse(response, object_class: OpenStruct)
|
124
152
|
end
|
153
|
+
end
|
125
154
|
|
126
|
-
|
127
|
-
password_hash = calc_password_hash(hash_spec["hashType"], password, hash_spec["salt"])
|
155
|
+
private
|
128
156
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
157
|
+
def make_rest_call(rest_url, method, body)
|
158
|
+
begin
|
159
|
+
response = RestClient::Request.execute(method: method, url: rest_url,
|
160
|
+
payload: body,
|
161
|
+
headers: { content_type: :json, accept: :json, authorization: @authString })
|
162
|
+
return response.body
|
163
|
+
rescue RestClient::NotFound
|
164
|
+
return "404"
|
134
165
|
end
|
166
|
+
end
|
135
167
|
|
136
|
-
|
137
|
-
|
138
|
-
when PasswordType::MD5
|
139
|
-
return Hashing.md5(password)
|
140
|
-
when PasswordType::SHA1
|
141
|
-
return Hashing.sha1(password)
|
142
|
-
when PasswordType::SHA256
|
143
|
-
return Hashing.sha256(password)
|
144
|
-
when PasswordType::SHA512
|
145
|
-
return Hashing.sha512(password)
|
146
|
-
when PasswordType::IPBoard_MyBB
|
147
|
-
if (salt != nil && salt.length > 0)
|
148
|
-
return Hashing.mybb(password, salt)
|
149
|
-
end
|
150
|
-
when PasswordType::VBulletinPre3_8_5
|
151
|
-
if (salt != nil && salt.length > 0)
|
152
|
-
return Hashing.vbulletin(password, salt)
|
153
|
-
end
|
154
|
-
when PasswordType::VBulletinPost3_8_5
|
155
|
-
if (salt != nil && salt.length > 0)
|
156
|
-
return Hashing.vbulletin(password, salt)
|
157
|
-
end
|
158
|
-
when PasswordType::BCrypt
|
159
|
-
if (salt != nil && salt.length > 0)
|
160
|
-
return Hashing.bcrypt(password, salt)
|
161
|
-
end
|
162
|
-
when PasswordType::CRC32
|
163
|
-
return Hashing.crc32(password)
|
164
|
-
when PasswordType::PHPBB3
|
165
|
-
if (salt != nil && salt.length > 0)
|
166
|
-
return Hashing.phpbb3(password, salt)
|
167
|
-
end
|
168
|
-
when PasswordType::CustomAlgorithm1
|
169
|
-
if (salt != nil && salt.length > 0)
|
170
|
-
return Hashing.custom_algorithm1(password, salt)
|
171
|
-
end
|
172
|
-
when PasswordType::CustomAlgorithm2
|
173
|
-
if (salt != nil && salt.length > 0)
|
174
|
-
return Hashing.custom_algorithm2(password, salt)
|
175
|
-
end
|
176
|
-
when PasswordType::MD5Crypt
|
177
|
-
if (salt != nil && salt.length > 0)
|
178
|
-
return Hashing.md5crypt(password, salt)
|
179
|
-
end
|
180
|
-
when PasswordType::CustomAlgorithm4
|
181
|
-
if (salt != nil && salt.length > 0)
|
182
|
-
return Hashing.custom_algorithm4(password, salt)
|
183
|
-
end
|
184
|
-
end
|
168
|
+
def calc_credential_hash(username, password, salt, hash_spec)
|
169
|
+
password_hash = calc_password_hash(hash_spec["hashType"], password, hash_spec["salt"])
|
185
170
|
|
171
|
+
if password_hash != nil
|
172
|
+
return Hashing.argon2_raw(username.downcase + "$" + password_hash, salt)
|
173
|
+
else
|
186
174
|
return nil
|
187
175
|
end
|
176
|
+
end
|
188
177
|
|
189
|
-
|
190
|
-
|
178
|
+
def calc_password_hash(password_type, password, salt)
|
179
|
+
case password_type
|
180
|
+
when PasswordType::MD5
|
181
|
+
return Hashing.md5(password)
|
182
|
+
when PasswordType::SHA1
|
183
|
+
return Hashing.sha1(password)
|
184
|
+
when PasswordType::SHA256
|
185
|
+
return Hashing.sha256(password)
|
186
|
+
when PasswordType::SHA512
|
187
|
+
return Hashing.sha512(password)
|
188
|
+
when PasswordType::IPBoard_MyBB
|
189
|
+
if salt != nil && salt.length > 0
|
190
|
+
return Hashing.mybb(password, salt)
|
191
|
+
end
|
192
|
+
when PasswordType::VBulletinPre3_8_5
|
193
|
+
if salt != nil && salt.length > 0
|
194
|
+
return Hashing.vbulletin(password, salt)
|
195
|
+
end
|
196
|
+
when PasswordType::VBulletinPost3_8_5
|
197
|
+
if salt != nil && salt.length > 0
|
198
|
+
return Hashing.vbulletin(password, salt)
|
199
|
+
end
|
200
|
+
when PasswordType::BCrypt
|
201
|
+
if salt != nil && salt.length > 0
|
202
|
+
return Hashing.bcrypt(password, salt)
|
203
|
+
end
|
204
|
+
when PasswordType::CRC32
|
205
|
+
return Hashing.crc32(password)
|
206
|
+
when PasswordType::PHPBB3
|
207
|
+
if salt != nil && salt.length > 0
|
208
|
+
return Hashing.phpbb3(password, salt)
|
209
|
+
end
|
210
|
+
when PasswordType::CustomAlgorithm1
|
211
|
+
if salt != nil && salt.length > 0
|
212
|
+
return Hashing.custom_algorithm1(password, salt)
|
213
|
+
end
|
214
|
+
when PasswordType::CustomAlgorithm2
|
215
|
+
if salt != nil && salt.length > 0
|
216
|
+
return Hashing.custom_algorithm2(password, salt)
|
217
|
+
end
|
218
|
+
when PasswordType::MD5Crypt
|
219
|
+
if salt != nil && salt.length > 0
|
220
|
+
return Hashing.md5crypt(password, salt)
|
221
|
+
end
|
222
|
+
when PasswordType::CustomAlgorithm4
|
223
|
+
if salt != nil && salt.length > 0
|
224
|
+
return Hashing.custom_algorithm4(password, salt)
|
225
|
+
end
|
226
|
+
when PasswordType::CustomAlgorithm5
|
227
|
+
if salt != nil && salt.length > 0
|
228
|
+
return Hashing.custom_algorithm5(password, salt)
|
229
|
+
end
|
230
|
+
when PasswordType::OsCommerce_AEF
|
231
|
+
if salt != nil && salt.length > 0
|
232
|
+
return Hashing.osCommerce_AEF(password, salt)
|
233
|
+
end
|
234
|
+
when PasswordType::DESCrypt
|
235
|
+
if salt != nil && salt.length > 0
|
236
|
+
return Hashing.desCrypt(password, salt)
|
237
|
+
end
|
238
|
+
when PasswordType::MySQLPre4_1
|
239
|
+
return Hashing.mySQLPre4_1(password)
|
240
|
+
when PasswordType::MySQLPost4_1
|
241
|
+
return Hashing.mySQLPost4_1(password)
|
242
|
+
when PasswordType::PunBB
|
243
|
+
if salt != nil && salt.length > 0
|
244
|
+
return Hashing.punBB(password, salt)
|
245
|
+
end
|
246
|
+
when PasswordType::CustomAlgorithm6
|
247
|
+
if salt != nil && salt.length > 0
|
248
|
+
return Hashing.custom_algorithm6(password, salt)
|
249
|
+
end
|
250
|
+
when PasswordType::PartialMD5_20
|
251
|
+
return Hashing.partial_md5_20(password)
|
252
|
+
when PasswordType::AVE_DataLife_Diferior
|
253
|
+
return Hashing.ave_datalife_diferior(password)
|
254
|
+
when PasswordType::DjangoMD5
|
255
|
+
if salt != nil && salt.length > 0
|
256
|
+
return Hashing.django_md5(password, salt)
|
257
|
+
end
|
258
|
+
when PasswordType::DjangoSHA1
|
259
|
+
if salt != nil && salt.length > 0
|
260
|
+
return Hashing.django_sha1(password, salt)
|
261
|
+
end
|
262
|
+
when PasswordType::PartialMD5_29
|
263
|
+
return Hashing.partial_md5_29(password)
|
264
|
+
when PasswordType::PliggCMS
|
265
|
+
if salt != nil && salt.length > 0
|
266
|
+
return Hashing.pligg_cms(password, salt)
|
267
|
+
end
|
268
|
+
when PasswordType::RunCMS_SMF1_1
|
269
|
+
if salt != nil && salt.length > 0
|
270
|
+
return Hashing.runcms_smf1_1(password, salt)
|
271
|
+
end
|
272
|
+
when PasswordType::NTLM
|
273
|
+
return Hashing.ntlm(password)
|
274
|
+
when PasswordType::SHA1Dash
|
275
|
+
if salt != nil && salt.length > 0
|
276
|
+
return Hashing.sha1dash(password, salt)
|
277
|
+
end
|
278
|
+
when PasswordType::SHA384
|
279
|
+
return Hashing.sha384(password)
|
280
|
+
when PasswordType::CustomAlgorithm7
|
281
|
+
if salt != nil && salt.length > 0
|
282
|
+
return Hashing.custom_algorithm7(password, salt)
|
283
|
+
end
|
284
|
+
when PasswordType::CustomAlgorithm9
|
285
|
+
if salt != nil && salt.length > 0
|
286
|
+
return Hashing.custom_algorithm9(password, salt)
|
287
|
+
end
|
288
|
+
when PasswordType::SHA512Crypt
|
289
|
+
if salt != nil && salt.length > 0
|
290
|
+
return Hashing.sha512crypt(password, salt)
|
291
|
+
end
|
292
|
+
when PasswordType::CustomAlgorithm10
|
293
|
+
if salt != nil && salt.length > 0
|
294
|
+
return Hashing.custom_algorithm10(password, salt)
|
295
|
+
end
|
296
|
+
when PasswordType::SHA256Crypt
|
297
|
+
if salt != nil && salt.length > 0
|
298
|
+
return Hashing.sha256crypt(password, salt)
|
299
|
+
end
|
300
|
+
when PasswordType::AuthMeSHA256
|
301
|
+
if salt != nil && salt.length > 0
|
302
|
+
return Hashing.authMeSHA256(password, salt)
|
303
|
+
end
|
304
|
+
when PasswordType::HMACSHA1_SaltAsHash
|
305
|
+
if salt != nil && salt.length > 0
|
306
|
+
return Hashing.hmac_sha1_salt_as_hash(password, salt)
|
307
|
+
end
|
308
|
+
else
|
309
|
+
return nil
|
191
310
|
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def calc_auth_string(api_key, secret)
|
314
|
+
return "basic " + Base64.strict_encode64(api_key + ":" + secret)
|
315
|
+
end
|
192
316
|
end
|
193
317
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: enzoic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Enzoic
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.15.5
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.15.5
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: ffi-compiler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,20 +118,20 @@ dependencies:
|
|
118
118
|
requirements:
|
119
119
|
- - "~>"
|
120
120
|
- !ruby/object:Gem::Version
|
121
|
-
version: 2.
|
121
|
+
version: 2.2.11
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: 2.
|
124
|
+
version: 2.2.11
|
125
125
|
type: :development
|
126
126
|
prerelease: false
|
127
127
|
version_requirements: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: 2.
|
131
|
+
version: 2.2.11
|
132
132
|
- - ">="
|
133
133
|
- !ruby/object:Gem::Version
|
134
|
-
version: 2.
|
134
|
+
version: 2.2.11
|
135
135
|
- !ruby/object:Gem::Dependency
|
136
136
|
name: rake
|
137
137
|
requirement: !ruby/object:Gem::Requirement
|
@@ -307,8 +307,8 @@ files:
|
|
307
307
|
- ext/phc-winner-argon2/src/genkat.c
|
308
308
|
- ext/phc-winner-argon2/src/genkat.h
|
309
309
|
- ext/phc-winner-argon2/src/opt.c
|
310
|
-
- ext/phc-winner-argon2/src/opt.o
|
311
310
|
- ext/phc-winner-argon2/src/ref.c
|
311
|
+
- ext/phc-winner-argon2/src/ref.o
|
312
312
|
- ext/phc-winner-argon2/src/run.c
|
313
313
|
- ext/phc-winner-argon2/src/test.c
|
314
314
|
- ext/phc-winner-argon2/src/thread.c
|
@@ -361,8 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
361
361
|
- !ruby/object:Gem::Version
|
362
362
|
version: '0'
|
363
363
|
requirements: []
|
364
|
-
|
365
|
-
rubygems_version: 2.5.2.3
|
364
|
+
rubygems_version: 3.1.6
|
366
365
|
signing_key:
|
367
366
|
specification_version: 4
|
368
367
|
summary: Ruby library for Enzoic API
|
Binary file
|