enzoic 1.0.3 → 1.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.
- checksums.yaml +5 -5
- data/README.md +22 -0
- data/enzoic.gemspec +1 -1
- data/ext/argon2-wrapper/.DS_Store +0 -0
- 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/Info.plist +20 -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/Info.plist +20 -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 +148 -7
- data/lib/enzoic/password_type.rb +26 -0
- data/lib/enzoic/version.rb +1 -1
- data/lib/enzoic.rb +231 -107
- metadata +26 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2af7917c4ee90414cdffeda3948ce2a0642f523f6c44fe07db64deab4f5b6e25
|
4
|
+
data.tar.gz: 4cdfeb61e75c47f95132e9a0c644f89a987597a48685038ac7308dba66e008ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 557fadc6dab20d5d76d2098466c31728ecba0a1fcd16ce9cd13a798817239bb791012691b37130b7e64dd7275728d83c9f89bdfeda10dbde0b369faf705f8ecf
|
7
|
+
data.tar.gz: 420af16ed93a7696969a74fbfcd13e491e23f934a9f15712fe42fc3eb777e6cdcf51751ac4ebc2bfa28b250fc7d17c3107f10989a524c4f1779811254423cc50
|
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,19 +43,40 @@ 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
|
48
50
|
puts("Credentials are not compromised")
|
49
51
|
end
|
50
52
|
|
53
|
+
# Check whether a specific set of credentials are compromised, using the optional
|
54
|
+
# lastCheckData parameter.
|
55
|
+
# lastCheckDate is the timestamp for the last check you performed for this user.
|
56
|
+
# If the DateTime you provide for the last check is greater than the timestamp Enzoic has
|
57
|
+
# for the last breach affecting this user, the check will not be performed.
|
58
|
+
# This can be used to substantially increase performance.
|
59
|
+
if enzoic.check_credentials("test@enzoic.com", "password-to-test", DateTime.parse("2019-07-15T19:57:43.000Z"))
|
60
|
+
puts("Credentials are compromised")
|
61
|
+
else
|
62
|
+
puts("Credentials are not compromised")
|
63
|
+
end
|
64
|
+
|
51
65
|
# get all exposures for a given user
|
66
|
+
# see https://www.enzoic.com/docs-exposures-api/#get-exposures for more information
|
52
67
|
exposures = enzoic.get_exposures_for_user("test@enzoic.com")
|
53
68
|
puts(exposures.count.to_s + " exposures found for test@enzoic.com")
|
54
69
|
|
55
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
|
56
72
|
details = enzoic.get_exposure_details(exposures.exposures[0])
|
57
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
|
+
|
58
80
|
```
|
59
81
|
|
60
82
|
More information in reference format can be found below.
|
data/enzoic.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
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
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
6
|
+
<string>English</string>
|
7
|
+
<key>CFBundleIdentifier</key>
|
8
|
+
<string>com.apple.xcode.dsym.argon2</string>
|
9
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
10
|
+
<string>6.0</string>
|
11
|
+
<key>CFBundlePackageType</key>
|
12
|
+
<string>dSYM</string>
|
13
|
+
<key>CFBundleSignature</key>
|
14
|
+
<string>????</string>
|
15
|
+
<key>CFBundleShortVersionString</key>
|
16
|
+
<string>1.0</string>
|
17
|
+
<key>CFBundleVersion</key>
|
18
|
+
<string>1</string>
|
19
|
+
</dict>
|
20
|
+
</plist>
|
Binary file
|
Binary file
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
6
|
+
<string>English</string>
|
7
|
+
<key>CFBundleIdentifier</key>
|
8
|
+
<string>com.apple.xcode.dsym.libargon2.0.dylib</string>
|
9
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
10
|
+
<string>6.0</string>
|
11
|
+
<key>CFBundlePackageType</key>
|
12
|
+
<string>dSYM</string>
|
13
|
+
<key>CFBundleSignature</key>
|
14
|
+
<string>????</string>
|
15
|
+
<key>CFBundleShortVersionString</key>
|
16
|
+
<string>1.0</string>
|
17
|
+
<key>CFBundleVersion</key>
|
18
|
+
<string>1</string>
|
19
|
+
</dict>
|
20
|
+
</plist>
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/enzoic/hashing.rb
CHANGED
@@ -24,6 +24,14 @@ module Enzoic
|
|
24
24
|
return Digest::SHA1.hexdigest to_hash
|
25
25
|
end
|
26
26
|
|
27
|
+
def self.sha1_binary(to_hash)
|
28
|
+
return Digest::SHA1.digest(to_hash).bytes
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.sha1_binary_array(to_hash_bytes)
|
32
|
+
return Digest::SHA1.digest(to_hash_bytes.pack('c*')).bytes
|
33
|
+
end
|
34
|
+
|
27
35
|
def self.sha256(to_hash)
|
28
36
|
return Digest::SHA256.hexdigest to_hash
|
29
37
|
end
|
@@ -91,7 +99,7 @@ module Enzoic
|
|
91
99
|
|
92
100
|
itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
93
101
|
to_hash_bytes = to_hash.bytes
|
94
|
-
count = 2**itoa64.index(salt[3])
|
102
|
+
count = 2 ** itoa64.index(salt[3])
|
95
103
|
justsalt = salt[4..12]
|
96
104
|
|
97
105
|
hash = self.md5_binary(justsalt + to_hash)
|
@@ -148,13 +156,146 @@ module Enzoic
|
|
148
156
|
end
|
149
157
|
|
150
158
|
def self.md5crypt(to_hash, salt)
|
151
|
-
return UnixCrypt::MD5.build(to_hash, salt.start_with?("$1$") ? salt[3..salt.length] : salt)
|
159
|
+
return UnixCrypt::MD5.build(to_hash, salt.start_with?("$1$") ? salt[3..salt.length] : salt)
|
152
160
|
end
|
153
161
|
|
154
162
|
def self.custom_algorithm4(to_hash, salt)
|
155
163
|
return self.bcrypt(self.md5(to_hash), salt)
|
156
164
|
end
|
157
165
|
|
166
|
+
def self.custom_algorithm5(to_hash, salt)
|
167
|
+
return self.sha256(self.md5(to_hash + salt))
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.osCommerce_AEF(to_hash, salt)
|
171
|
+
return self.md5(salt + to_hash)
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.desCrypt(to_hash, salt)
|
175
|
+
return UnixCrypt::DES.build(to_hash, salt)
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.convertToUnsigned(val)
|
179
|
+
return [val].pack('L').unpack('L').first
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.mySQLPre4_1(to_hash)
|
183
|
+
nr = 1345345333
|
184
|
+
add = 7
|
185
|
+
nr2 = 0x12345671
|
186
|
+
|
187
|
+
for i in 0..to_hash.length - 1 do
|
188
|
+
c = to_hash[i]
|
189
|
+
|
190
|
+
if c == " " || c == "\t"
|
191
|
+
next
|
192
|
+
end
|
193
|
+
|
194
|
+
tmp = c.ord
|
195
|
+
nr = nr ^ ((((nr & 63) + add) * tmp) + (self.convertToUnsigned(nr << 8)))
|
196
|
+
nr2 += (self.convertToUnsigned(nr2 << 8)) ^ nr
|
197
|
+
add += tmp
|
198
|
+
end
|
199
|
+
|
200
|
+
result1 = nr & ((self.convertToUnsigned(1 << 31)) - 1)
|
201
|
+
result2 = nr2 & ((self.convertToUnsigned(1 << 31)) - 1)
|
202
|
+
|
203
|
+
return result1.to_s(16) + result2.to_s(16)
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.mySQLPost4_1(to_hash)
|
207
|
+
return "*" + self.bytes_to_hex(self.sha1_binary_array(self.sha1_binary(to_hash)));
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.punBB(to_hash, salt)
|
211
|
+
return self.sha1(salt + self.sha1(to_hash))
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.custom_algorithm6(to_hash, salt)
|
215
|
+
return self.sha1(to_hash + salt)
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.partial_md5_20(to_hash)
|
219
|
+
return self.md5(to_hash)[0..19]
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.partial_md5_29(to_hash)
|
223
|
+
return self.md5(to_hash)[0..28]
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.ave_datalife_diferior(to_hash)
|
227
|
+
return self.md5(self.md5(to_hash))
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.django_md5(to_hash, salt)
|
231
|
+
return "md5$" + salt + "$" + self.md5(salt + to_hash)
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.django_sha1(to_hash, salt)
|
235
|
+
return "sha1$" + salt + "$" + self.sha1(salt + to_hash)
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.pligg_cms(to_hash, salt)
|
239
|
+
return salt + self.sha1(salt + to_hash)
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.runcms_smf1_1(to_hash, salt)
|
243
|
+
return self.sha1(salt + to_hash)
|
244
|
+
end
|
245
|
+
|
246
|
+
def self.ntlm(to_hash)
|
247
|
+
pwd = to_hash.dup
|
248
|
+
pwd = pwd.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
|
249
|
+
OpenSSL::Digest::MD4.hexdigest pwd
|
250
|
+
end
|
251
|
+
|
252
|
+
def self.sha1dash(to_hash, salt)
|
253
|
+
return self.sha1("--" + salt + "--" + to_hash + "--")
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.sha384(to_hash)
|
257
|
+
return Digest::SHA384.hexdigest to_hash
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.custom_algorithm7(to_hash, salt)
|
261
|
+
derived_salt = self.sha1(salt)
|
262
|
+
return OpenSSL::HMAC.hexdigest("SHA256",
|
263
|
+
"d2e1a4c569e7018cc142e9cce755a964bd9b193d2d31f02d80bb589c959afd7e",
|
264
|
+
derived_salt + to_hash)
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.custom_algorithm9(to_hash, salt)
|
268
|
+
result = self.sha512(to_hash + salt)
|
269
|
+
for i in 0..10 do
|
270
|
+
result = self.sha512(result)
|
271
|
+
end
|
272
|
+
return result
|
273
|
+
end
|
274
|
+
|
275
|
+
def self.sha512crypt(to_hash, salt)
|
276
|
+
return UnixCrypt::SHA512.build(to_hash, salt.start_with?("$6$") ? salt[3..salt.length] : salt)
|
277
|
+
end
|
278
|
+
|
279
|
+
def self.custom_algorithm10(to_hash, salt)
|
280
|
+
return self.sha512(to_hash + ":" + salt)
|
281
|
+
end
|
282
|
+
|
283
|
+
def self.sha256crypt(to_hash, salt)
|
284
|
+
salt_to_use = salt
|
285
|
+
if salt_to_use.start_with?("$5$")
|
286
|
+
salt_to_use = salt_to_use[3..salt.length];
|
287
|
+
end
|
288
|
+
if salt_to_use.start_with?("rounds=")
|
289
|
+
salt_to_use = salt_to_use[salt_to_use.index("$") + 1..salt_to_use.length]
|
290
|
+
end
|
291
|
+
|
292
|
+
return UnixCrypt::SHA256.build(to_hash, salt_to_use)
|
293
|
+
end
|
294
|
+
|
295
|
+
def self.authMeSHA256(to_hash, salt)
|
296
|
+
return "$SHA$" + salt + "$" + self.sha256(self.sha256(to_hash) + salt);
|
297
|
+
end
|
298
|
+
|
158
299
|
def self.argon2_raw(to_hash, salt)
|
159
300
|
time_cost = 3
|
160
301
|
mem_cost = 10
|
@@ -238,13 +379,13 @@ module Enzoic
|
|
238
379
|
end
|
239
380
|
|
240
381
|
def self.xor(byte_array1, byte_array2)
|
241
|
-
|
382
|
+
result = Array.new(byte_array1.length);
|
242
383
|
|
243
|
-
|
244
|
-
|
245
|
-
|
384
|
+
for i in 0..byte_array1.length - 1 do
|
385
|
+
result[i] = byte_array1[i] ^ byte_array2[i];
|
386
|
+
end
|
246
387
|
|
247
|
-
|
388
|
+
return result;
|
248
389
|
end
|
249
390
|
|
250
391
|
def self.bytes_to_hex(bytes)
|
data/lib/enzoic/password_type.rb
CHANGED
@@ -18,6 +18,32 @@ 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
|
+
SHA256Crypt = 41
|
45
|
+
AuthMeSHA256 = 42
|
46
|
+
|
21
47
|
Unknown = 97
|
22
48
|
UnusablePassword = 98
|
23
49
|
None = 99
|
data/lib/enzoic/version.rb
CHANGED
data/lib/enzoic.rb
CHANGED
@@ -15,79 +15,109 @@ 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
|
-
hashes_required = account_response["passwordHashesRequired"]
|
37
38
|
|
38
|
-
|
39
|
-
|
39
|
+
# if lastCheckTimestamp was provided, see if we need to go any further
|
40
|
+
if Date.parse(account_response["lastBreachDate"]) > last_check_timestamp
|
41
|
+
hashes_required = account_response["passwordHashesRequired"]
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
+
bcrypt_count = 0
|
44
|
+
query_string = ""
|
45
|
+
credential_hashes = []
|
43
46
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
if
|
49
|
-
|
50
|
-
|
47
|
+
hashes_required.each do |hash_spec|
|
48
|
+
# bcrypt gets far too expensive for good response time if there are many of them to calculate.
|
49
|
+
# some mostly garbage accounts have accumulated a number of them in our DB and if we happen to hit one it
|
50
|
+
# kills performance, so short circuit out after at most 2 BCrypt hashes
|
51
|
+
if hash_spec["hashType"] != PasswordType::BCrypt || bcrypt_count <= 2
|
52
|
+
if hash_spec["hashType"] == PasswordType::BCrypt
|
53
|
+
bcrypt_count = bcrypt_count + 1
|
54
|
+
end
|
55
|
+
|
56
|
+
if hash_spec["hashType"] != nil
|
57
|
+
credential_hash = calc_credential_hash(username.downcase, password, account_response["salt"], hash_spec)
|
51
58
|
|
52
|
-
|
53
|
-
|
59
|
+
if credential_hash != nil
|
60
|
+
credential_hashes << credential_hash
|
54
61
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
62
|
+
if query_string.length == 0
|
63
|
+
query_string = query_string + "?partialHashes=" + CGI.escape(credential_hash[0..6])
|
64
|
+
else
|
65
|
+
query_string = query_string + "&partialHashes=" + CGI.escape(credential_hash[0..6])
|
66
|
+
end
|
60
67
|
end
|
61
68
|
end
|
62
69
|
end
|
63
70
|
end
|
64
|
-
end
|
65
71
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
72
|
+
if query_string.length > 0
|
73
|
+
creds_response = make_rest_call(
|
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
|
84
|
+
end
|
70
85
|
end
|
71
86
|
|
72
87
|
return false
|
73
88
|
end
|
74
89
|
|
75
90
|
def check_password(password)
|
91
|
+
md5 = Hashing.md5(password)
|
92
|
+
sha1 = Hashing.sha1(password)
|
93
|
+
sha256 = Hashing.sha256(password)
|
94
|
+
|
76
95
|
response = make_rest_call(
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
96
|
+
@baseURL + Constants::PASSWORDS_API_PATH, "POST",
|
97
|
+
'{' +
|
98
|
+
'"partialMD5":"' + md5[0..6] + '",' +
|
99
|
+
'"partialSHA1":"' + sha1[0..6] + '",' +
|
100
|
+
'"partialSHA256":"' + sha256[0..6] + '"' +
|
101
|
+
'}')
|
102
|
+
|
103
|
+
if response != "404"
|
104
|
+
result = JSON.parse(response, object_class: OpenStruct)
|
82
105
|
|
83
|
-
|
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
|
84
114
|
end
|
85
115
|
|
86
116
|
def get_exposures_for_user(username)
|
87
|
-
response = make_rest_call(@baseURL + Constants::EXPOSURES_API_PATH + "?username=" + Hashing.sha256(username),
|
88
|
-
|
117
|
+
response = make_rest_call(@baseURL + Constants::EXPOSURES_API_PATH + "?username=" + Hashing.sha256(username.downcase),
|
118
|
+
"GET", nil)
|
89
119
|
|
90
|
-
if
|
120
|
+
if response == "404"
|
91
121
|
# don't have this email in the DB - return empty response
|
92
122
|
return JSON.parse('{ "count": 0, "exposures": [] }', object_class: OpenStruct)
|
93
123
|
else
|
@@ -98,9 +128,9 @@ module Enzoic
|
|
98
128
|
|
99
129
|
def get_exposure_details(exposure_id)
|
100
130
|
response = make_rest_call(@baseURL + Constants::EXPOSURES_API_PATH + "?id=" + CGI.escape(exposure_id),
|
101
|
-
|
131
|
+
"GET", nil)
|
102
132
|
|
103
|
-
if
|
133
|
+
if response != "404"
|
104
134
|
# deserialize response
|
105
135
|
return JSON.parse(response, object_class: OpenStruct)
|
106
136
|
else
|
@@ -108,82 +138,176 @@ module Enzoic
|
|
108
138
|
end
|
109
139
|
end
|
110
140
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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)
|
120
152
|
end
|
153
|
+
end
|
121
154
|
|
122
|
-
|
123
|
-
password_hash = calc_password_hash(hash_spec["hashType"], password, hash_spec["salt"])
|
155
|
+
private
|
124
156
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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"
|
130
165
|
end
|
166
|
+
end
|
131
167
|
|
132
|
-
|
133
|
-
|
134
|
-
when PasswordType::MD5
|
135
|
-
return Hashing.md5(password)
|
136
|
-
when PasswordType::SHA1
|
137
|
-
return Hashing.sha1(password)
|
138
|
-
when PasswordType::SHA256
|
139
|
-
return Hashing.sha256(password)
|
140
|
-
when PasswordType::SHA512
|
141
|
-
return Hashing.sha512(password)
|
142
|
-
when PasswordType::IPBoard_MyBB
|
143
|
-
if (salt != nil && salt.length > 0)
|
144
|
-
return Hashing.mybb(password, salt)
|
145
|
-
end
|
146
|
-
when PasswordType::VBulletinPre3_8_5
|
147
|
-
if (salt != nil && salt.length > 0)
|
148
|
-
return Hashing.vbulletin(password, salt)
|
149
|
-
end
|
150
|
-
when PasswordType::VBulletinPost3_8_5
|
151
|
-
if (salt != nil && salt.length > 0)
|
152
|
-
return Hashing.vbulletin(password, salt)
|
153
|
-
end
|
154
|
-
when PasswordType::BCrypt
|
155
|
-
if (salt != nil && salt.length > 0)
|
156
|
-
return Hashing.bcrypt(password, salt)
|
157
|
-
end
|
158
|
-
when PasswordType::CRC32
|
159
|
-
return Hashing.crc32(password)
|
160
|
-
when PasswordType::PHPBB3
|
161
|
-
if (salt != nil && salt.length > 0)
|
162
|
-
return Hashing.phpbb3(password, salt)
|
163
|
-
end
|
164
|
-
when PasswordType::CustomAlgorithm1
|
165
|
-
if (salt != nil && salt.length > 0)
|
166
|
-
return Hashing.custom_algorithm1(password, salt)
|
167
|
-
end
|
168
|
-
when PasswordType::CustomAlgorithm2
|
169
|
-
if (salt != nil && salt.length > 0)
|
170
|
-
return Hashing.custom_algorithm2(password, salt)
|
171
|
-
end
|
172
|
-
when PasswordType::MD5Crypt
|
173
|
-
if (salt != nil && salt.length > 0)
|
174
|
-
return Hashing.md5crypt(password, salt)
|
175
|
-
end
|
176
|
-
when PasswordType::CustomAlgorithm4
|
177
|
-
if (salt != nil && salt.length > 0)
|
178
|
-
return Hashing.custom_algorithm4(password, salt)
|
179
|
-
end
|
180
|
-
end
|
168
|
+
def calc_credential_hash(username, password, salt, hash_spec)
|
169
|
+
password_hash = calc_password_hash(hash_spec["hashType"], password, hash_spec["salt"])
|
181
170
|
|
171
|
+
if password_hash != nil
|
172
|
+
return Hashing.argon2_raw(username.downcase + "$" + password_hash, salt)
|
173
|
+
else
|
182
174
|
return nil
|
183
175
|
end
|
176
|
+
end
|
184
177
|
|
185
|
-
|
186
|
-
|
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
|
+
else
|
305
|
+
return nil
|
187
306
|
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def calc_auth_string(api_key, secret)
|
310
|
+
return "basic " + Base64.strict_encode64(api_key + ":" + secret)
|
311
|
+
end
|
188
312
|
end
|
189
313
|
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.0
|
4
|
+
version: 1.2.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: 2022-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -116,22 +116,22 @@ dependencies:
|
|
116
116
|
name: bundler
|
117
117
|
requirement: !ruby/object:Gem::Requirement
|
118
118
|
requirements:
|
119
|
-
- - "~>"
|
120
|
-
- !ruby/object:Gem::Version
|
121
|
-
version: 2.0.2
|
122
119
|
- - ">="
|
123
120
|
- !ruby/object:Gem::Version
|
124
|
-
version: 2.
|
121
|
+
version: 2.2.11
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
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
|
-
- - "~>"
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: 2.0.2
|
132
129
|
- - ">="
|
133
130
|
- !ruby/object:Gem::Version
|
134
|
-
version: 2.
|
131
|
+
version: 2.2.11
|
132
|
+
- - "~>"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 2.2.11
|
135
135
|
- !ruby/object:Gem::Dependency
|
136
136
|
name: rake
|
137
137
|
requirement: !ruby/object:Gem::Requirement
|
@@ -230,9 +230,11 @@ files:
|
|
230
230
|
- Rakefile
|
231
231
|
- enzoic.gemspec
|
232
232
|
- ext/.DS_Store
|
233
|
+
- ext/argon2-wrapper/.DS_Store
|
233
234
|
- ext/argon2-wrapper/Makefile
|
234
235
|
- ext/argon2-wrapper/argon2-wrapper.c
|
235
236
|
- ext/argon2-wrapper/extconf.rb
|
237
|
+
- ext/argon2-wrapper/libargon2-wrapper.bundle
|
236
238
|
- ext/argon2_import/.DS_Store
|
237
239
|
- ext/digest/whirlpool/extconf.rb
|
238
240
|
- ext/digest/whirlpool/whirlpool-algorithm.c
|
@@ -251,7 +253,10 @@ files:
|
|
251
253
|
- ext/phc-winner-argon2/Makefile
|
252
254
|
- ext/phc-winner-argon2/README.md
|
253
255
|
- ext/phc-winner-argon2/appveyor.yml
|
256
|
+
- ext/phc-winner-argon2/argon2
|
254
257
|
- ext/phc-winner-argon2/argon2-specs.pdf
|
258
|
+
- ext/phc-winner-argon2/argon2.dSYM/Contents/Info.plist
|
259
|
+
- ext/phc-winner-argon2/argon2.dSYM/Contents/Resources/DWARF/argon2
|
255
260
|
- ext/phc-winner-argon2/export.sh
|
256
261
|
- ext/phc-winner-argon2/include/argon2.h
|
257
262
|
- ext/phc-winner-argon2/kats/argon2d
|
@@ -278,27 +283,37 @@ files:
|
|
278
283
|
- ext/phc-winner-argon2/latex/pics/generic.pdf
|
279
284
|
- ext/phc-winner-argon2/latex/pics/power-distribution.jpg
|
280
285
|
- ext/phc-winner-argon2/latex/tradeoff.bib
|
286
|
+
- ext/phc-winner-argon2/libargon2.0.dylib
|
287
|
+
- ext/phc-winner-argon2/libargon2.0.dylib.dSYM/Contents/Info.plist
|
288
|
+
- ext/phc-winner-argon2/libargon2.0.dylib.dSYM/Contents/Resources/DWARF/libargon2.0.dylib
|
289
|
+
- ext/phc-winner-argon2/libargon2.a
|
281
290
|
- ext/phc-winner-argon2/libargon2.pc
|
282
291
|
- ext/phc-winner-argon2/man/argon2.1
|
283
292
|
- ext/phc-winner-argon2/src/argon2.c
|
293
|
+
- ext/phc-winner-argon2/src/argon2.o
|
284
294
|
- ext/phc-winner-argon2/src/bench.c
|
285
295
|
- ext/phc-winner-argon2/src/blake2/blake2-impl.h
|
286
296
|
- ext/phc-winner-argon2/src/blake2/blake2.h
|
287
297
|
- ext/phc-winner-argon2/src/blake2/blake2b.c
|
298
|
+
- ext/phc-winner-argon2/src/blake2/blake2b.o
|
288
299
|
- ext/phc-winner-argon2/src/blake2/blamka-round-opt.h
|
289
300
|
- ext/phc-winner-argon2/src/blake2/blamka-round-ref.h
|
290
301
|
- ext/phc-winner-argon2/src/core.c
|
291
302
|
- ext/phc-winner-argon2/src/core.h
|
303
|
+
- ext/phc-winner-argon2/src/core.o
|
292
304
|
- ext/phc-winner-argon2/src/encoding.c
|
293
305
|
- ext/phc-winner-argon2/src/encoding.h
|
306
|
+
- ext/phc-winner-argon2/src/encoding.o
|
294
307
|
- ext/phc-winner-argon2/src/genkat.c
|
295
308
|
- ext/phc-winner-argon2/src/genkat.h
|
296
309
|
- ext/phc-winner-argon2/src/opt.c
|
297
310
|
- ext/phc-winner-argon2/src/ref.c
|
311
|
+
- ext/phc-winner-argon2/src/ref.o
|
298
312
|
- ext/phc-winner-argon2/src/run.c
|
299
313
|
- ext/phc-winner-argon2/src/test.c
|
300
314
|
- ext/phc-winner-argon2/src/thread.c
|
301
315
|
- ext/phc-winner-argon2/src/thread.h
|
316
|
+
- ext/phc-winner-argon2/src/thread.o
|
302
317
|
- ext/phc-winner-argon2/vs2015/Argon2Opt/Argon2Opt.vcxproj
|
303
318
|
- ext/phc-winner-argon2/vs2015/Argon2Opt/Argon2Opt.vcxproj.filters
|
304
319
|
- ext/phc-winner-argon2/vs2015/Argon2OptBench/Argon2OptBench.vcxproj
|
@@ -346,8 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
346
361
|
- !ruby/object:Gem::Version
|
347
362
|
version: '0'
|
348
363
|
requirements: []
|
349
|
-
|
350
|
-
rubygems_version: 2.5.2.3
|
364
|
+
rubygems_version: 3.0.3.1
|
351
365
|
signing_key:
|
352
366
|
specification_version: 4
|
353
367
|
summary: Ruby library for Enzoic API
|