-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLeakHunter.java
More file actions
485 lines (428 loc) · 25.2 KB
/
LeakHunter.java
File metadata and controls
485 lines (428 loc) · 25.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
import java.util.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// Import rimossi: tutte le classi sono ora nel root e senza package
/**
* LeakHunter
* Modulo Java: AnalysisEngine
*
* Esegue analisi statistica e rilevamento pattern su password aziendali.
* NON esegue attacchi reali. Calcola risk score basato su:
* - Pattern dizionario comune
* - Sequenze tastiera
* - Pattern data/anno
* - Pattern aziendali personalizzati
* - Leet speak su parole di dizionario
* - Entropia Shannon
* - Stima tempo di crack (matematica)
*
* Utilizzo consigliato: integrazione come servlet o CLI per audit interni.
*/
public class PasswordAnalysisEngine {
// Checker modulari aggiuntivi
private static final PasswordPatternChecker DATE_PATTERN_CHECKER =
new DatePatternChecker(
Arrays.asList(
"gennaio","febbraio","marzo","aprile","maggio","giugno",
"luglio","agosto","settembre","ottobre","novembre","dicembre",
"gen","feb","mar","apr","mag","giu","lug","ago","set","ott","nov","dic"
),
1980, 2030
);
// CorporatePatternChecker sarà istanziato dinamicamente in base al context
private static final PasswordPatternChecker LEET_SPEAK_CHECKER =
new LeetSpeakChecker(
new HashSet<>(Arrays.asList(
"password","password1","123456","12345678","qwerty","abc123",
"letmein","monkey","master","dragon","111111","baseball",
"iloveyou","trustno1","sunshine","princess","welcome","shadow",
"superman","michael","football","admin","login","pass","test",
"hello","guest","access","batman","summer","winter","spring",
"autumn","ciao","italia","roma","milano","napoli","torino",
"firenze","venezia","juventus","inter","milan"
)),
new HashMap<Character, Character>() {{
put('@', 'a'); put('3', 'e'); put('1', 'i');
put('0', 'o'); put('5', 's'); put('7', 't');
put('4', 'a'); put('$', 's');
}}
);
// ── Costanti di analisi ──────────────────────────────────────────
private static final long HASH_RATE_OFFLINE_GPU = 10_000_000_000L; // SHA-256 GPU ~10B/s
private static final long HASH_RATE_BCRYPT_GPU = 10_000L; // bcrypt GPU ~10k/s
private static final long HASH_RATE_NTLM_CLUSTER= 1_000_000_000_000L; // NTLM cluster
// Checker modulari (inizializzati con dati statici per esempio)
private static final PasswordPatternChecker COMMON_PASSWORD_CHECKER =
new CommonPasswordChecker(new HashSet<>(Arrays.asList(
"password","password1","123456","12345678","qwerty","abc123",
"letmein","monkey","master","dragon","111111","baseball",
"iloveyou","trustno1","sunshine","princess","welcome","shadow",
"superman","michael","football","admin","login","pass","test",
"hello","guest","access","batman","summer","winter","spring",
"autumn","ciao","italia","roma","milano","napoli","torino",
"firenze","venezia","juventus","inter","milan"
)));
private static final PasswordPatternChecker KEYBOARD_PATTERN_CHECKER =
new KeyboardPatternChecker(Arrays.asList(
"qwerty","qwertyui","asdf","asdfgh","zxcv","zxcvbn",
"1234","12345","123456","1234567","12345678","987654",
"qazwsx","qazwsxedc","1q2w3e","1q2w3e4r","abcd","abcde"
));
// ...
// ── Strutture dati risultato ─────────────────────────────────────
// Classe Finding ora pubblica in com.peit.engine.model
public static class AnalysisResult {
public final String user;
public final String department;
public final int length;
public final int riskScore; // 0–100
public final double entropy; // bit
public final String crackTimeGPU; // SHA-256, 10B hash/s
public final String crackTimeBcrypt;// bcrypt, 10k hash/s
public final String crackTimeNTLM; // NTLM cluster
public final Map<String, Integer> patterns; // pattern → score 0-100
public final List<Finding> findings;
public final List<String> recommendations;
public final List<String> tags;
public final LocalDateTime timestamp;
public AnalysisResult(String user, String dept, int length, int risk,
double entropy, String crackGPU, String crackBcrypt, String crackNTLM,
Map<String,Integer> patterns, List<Finding> findings,
List<String> recs, List<String> tags) {
this.user = user;
this.department = dept;
this.length = length;
this.riskScore = risk;
this.entropy = entropy;
this.crackTimeGPU = crackGPU;
this.crackTimeBcrypt = crackBcrypt;
this.crackTimeNTLM = crackNTLM;
this.patterns = Collections.unmodifiableMap(patterns);
this.findings = Collections.unmodifiableList(findings);
this.recommendations = Collections.unmodifiableList(recs);
this.tags = Collections.unmodifiableList(tags);
this.timestamp = LocalDateTime.now();
}
public String getRiskLevel() {
if (riskScore >= 75) return "CRITICO";
if (riskScore >= 50) return "ALTO";
if (riskScore >= 25) return "MEDIO";
return "BASSO";
}
/** Formattazione report testuale */
public String toReport() {
StringBuilder sb = new StringBuilder();
sb.append("═══════════════════════════════════════\n");
sb.append(" LeakHunter — Password Analysis\n");
sb.append("═══════════════════════════════════════\n");
sb.append(String.format(" Utente: %s\n", user));
sb.append(String.format(" Reparto: %s\n", department));
sb.append(String.format(" Data/Ora: %s\n",
timestamp.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"))));
sb.append("───────────────────────────────────────\n");
sb.append(String.format(" Risk Score: %d/100 [%s]\n", riskScore, getRiskLevel()));
sb.append(String.format(" Entropia: %.1f bit\n", entropy));
sb.append(String.format(" Lunghezza: %d caratteri\n", length));
sb.append(String.format(" Crack (GPU): %s\n", crackTimeGPU));
sb.append(String.format(" Crack (NTLM): %s\n", crackTimeNTLM));
sb.append("───────────────────────────────────────\n");
sb.append(" VULNERABILITÀ RILEVATE:\n");
if (findings.isEmpty()) {
sb.append(" Nessuna vulnerabilità critica rilevata.\n");
} else {
for (Finding f : findings) {
sb.append(String.format(" ▸ [%s] %s\n", f.severity, f.title));
sb.append(String.format(" %s\n", f.detail));
}
}
sb.append("───────────────────────────────────────\n");
sb.append(" RACCOMANDAZIONI:\n");
recommendations.forEach(r -> sb.append(" • ").append(r).append("\n"));
sb.append("═══════════════════════════════════════\n");
return sb.toString();
}
}
// ── Opzioni di analisi ───────────────────────────────────────────
public static class AnalysisOptions {
public boolean checkDictionary = true;
public boolean checkKeyboardWalks = true;
public boolean checkDatePatterns = true;
public boolean checkCorporate = true;
public boolean checkLeetSpeak = true;
public String companyContext = ""; // nome, anno, settore azienda
public static AnalysisOptions defaults() {
return new AnalysisOptions();
}
public AnalysisOptions withCompany(String company) {
this.companyContext = company;
return this;
}
}
// ── Engine principale ────────────────────────────────────────────
/**
* Analizza una singola password e restituisce un AnalysisResult.
*
* @param password La password da analizzare (mai loggata o persistita)
* @param user Nome utente / identificativo
* @param dept Reparto/team
* @param opts Opzioni di analisi
* @return AnalysisResult con risk score, findings e raccomandazioni
*/
public AnalysisResult analyze(String password, String user, String dept, AnalysisOptions opts) {
if (password == null || password.isEmpty()) {
return buildEmptyResult(user, dept);
}
String pwLow = password.toLowerCase();
Map<String, Integer> patterns = new LinkedHashMap<>();
List<Finding> findings = new ArrayList<>();
List<String> tags = new ArrayList<>();
int riskPenalty = 0;
// ── 1. Entropia Shannon ──────────────────────────────────────
int charspace = computeCharspace(password);
double entropy = charspace > 0
? Math.log(charspace) / Math.log(2) * password.length()
: 0;
// ── 2. Pattern: Dizionario comune (modulare) ───────────────
if (opts.checkDictionary) {
List<Finding> commonFindings = COMMON_PASSWORD_CHECKER.check(password);
boolean isCommon = !commonFindings.isEmpty();
patterns.put(COMMON_PASSWORD_CHECKER.getName(), isCommon ? 95 : 5);
if (isCommon) {
riskPenalty += 45;
findings.addAll(commonFindings);
tags.add("DICTIONARY");
}
}
// ── 3. Pattern: Sequenze tastiera (modulare) ───────────────
if (opts.checkKeyboardWalks) {
List<Finding> kbdFindings = KEYBOARD_PATTERN_CHECKER.check(password);
boolean isKbd = !kbdFindings.isEmpty();
patterns.put(KEYBOARD_PATTERN_CHECKER.getName(), isKbd ? 85 : 5);
if (isKbd) {
riskPenalty += 35;
findings.addAll(kbdFindings);
tags.add("KEYBOARD WALK");
}
}
// ── 4. Pattern: Data / Anno (modulare) ─────────────────────
if (opts.checkDatePatterns) {
List<Finding> dateFindings = DATE_PATTERN_CHECKER.check(password);
boolean hasDatePattern = !dateFindings.isEmpty();
patterns.put(DATE_PATTERN_CHECKER.getName(), hasDatePattern ? 90 : 5);
if (hasDatePattern) {
riskPenalty += 25;
findings.addAll(dateFindings);
tags.add("DATE PATTERN");
}
}
// ── 5. Pattern: Aziendale (modulare) ───────────────────────
if (opts.checkCorporate && opts.companyContext != null && !opts.companyContext.isBlank()) {
List<String> companyParts = Arrays.asList(opts.companyContext.toLowerCase().split("[\\s,]+"));
PasswordPatternChecker corpChecker = new CorporatePatternChecker(companyParts);
List<Finding> corpFindings = corpChecker.check(password);
boolean hasCorp = !corpFindings.isEmpty();
patterns.put(corpChecker.getName(), hasCorp ? 88 : 3);
if (hasCorp) {
riskPenalty += 40;
findings.addAll(corpFindings);
tags.add("CORPORATE");
}
}
// ── 6. Pattern: Leet speak (modulare) ──────────────────────
if (opts.checkLeetSpeak) {
List<Finding> leetFindings = LEET_SPEAK_CHECKER.check(password);
boolean isLeet = !leetFindings.isEmpty();
patterns.put(LEET_SPEAK_CHECKER.getName(), isLeet ? 70 : 3);
if (isLeet) {
riskPenalty += 20;
findings.addAll(leetFindings);
tags.add("LEET SPEAK");
}
}
// ── 7. Caratteri ripetuti ────────────────────────────────────
if (password.matches(".*(.)\\1{2,}.*")) {
patterns.put("Caratteri ripetuti", 60);
riskPenalty += 15;
findings.add(new Finding(Finding.Severity.MEDIUM,
"Caratteri ripetuti consecutivi",
"Sequenze tipo \"aaa\" o \"111\" abbassano l'entropia effettiva."));
tags.add("REPETITION");
} else {
patterns.put("Caratteri ripetuti", 5);
}
// ── 8. Charset coverage ──────────────────────────────────────
List<String> missingCharsets = new ArrayList<>();
if (!password.matches(".*[a-z].*")) missingCharsets.add("minuscole");
if (!password.matches(".*[A-Z].*")) missingCharsets.add("maiuscole");
if (!password.matches(".*[0-9].*")) missingCharsets.add("cifre");
if (!password.matches(".*[^A-Za-z0-9].*")) missingCharsets.add("simboli");
patterns.put("Varietà charset", Math.min(missingCharsets.size() * 20, 80));
if (missingCharsets.size() > 1) {
riskPenalty += missingCharsets.size() * 8;
findings.add(new Finding(Finding.Severity.MEDIUM,
"Charset limitato — mancano: " + String.join(", ", missingCharsets),
"Ogni tipo di carattere aggiuntivo aumenta esponenzialmente lo spazio di ricerca."));
}
// ── 9. Lunghezza ─────────────────────────────────────────────
patterns.put("Lunghezza insufficiente",
password.length() < 12 ? Math.max(0, (12 - password.length()) * 8) : 5);
if (password.length() < 8) {
riskPenalty += 50;
findings.add(new Finding(Finding.Severity.CRITICAL,
"Lunghezza critica: " + password.length() + " caratteri",
"Password < 8 caratteri sono esaurite in secondi anche con hardware consumer."));
tags.add("TOO SHORT");
} else if (password.length() < 12) {
riskPenalty += 15;
findings.add(new Finding(Finding.Severity.MEDIUM,
"Lunghezza borderline: " + password.length() + " caratteri",
"Il NIST SP 800-63B raccomanda minimo 12 caratteri per account aziendali."));
}
// ── 10. Nome utente incluso ──────────────────────────────────
if (user != null && user.length() > 2) {
String[] userParts = user.toLowerCase().split("[.\\-_@]");
boolean userHit = Arrays.stream(userParts)
.filter(p -> p.length() > 2)
.anyMatch(pwLow::contains);
patterns.put("Nome utente incluso", userHit ? 85 : 3);
if (userHit) {
riskPenalty += 30;
findings.add(new Finding(Finding.Severity.HIGH,
"Nome utente incluso nella password",
"Includere username o nome è una delle prime varianti testate in un attacco personalizzato."));
tags.add("USERNAME");
}
}
// ── Risk Score finale ────────────────────────────────────────
double baseRisk = Math.max(0, 80 - entropy * 0.6);
int riskScore = Math.min(100, (int) Math.round(baseRisk + riskPenalty * 0.7));
// Ordina findings per severità
findings.sort(Comparator.comparing(f -> f.severity.ordinal()));
// ── Crack time ───────────────────────────────────────────────
double combinations = Math.pow(charspace > 0 ? charspace : 1, password.length());
String crackGPU = formatCrackTime(combinations / 2 / HASH_RATE_OFFLINE_GPU);
String crackBcrypt = formatCrackTime(combinations / 2 / HASH_RATE_BCRYPT_GPU);
String crackNTLM = formatCrackTime(combinations / 2 / HASH_RATE_NTLM_CLUSTER);
// ── Raccomandazioni ──────────────────────────────────────────
List<String> recs = generateRecommendations(riskScore, entropy, password.length(),
findings, tags, missingCharsets);
return new AnalysisResult(user, dept, password.length(), riskScore,
Math.round(entropy * 10.0) / 10.0,
crackGPU, crackBcrypt, crackNTLM,
patterns, findings, recs, tags);
}
// ── Analisi batch ────────────────────────────────────────────────
/**
* Analizza un intero reparto (lista di password).
* Restituisce lista di risultati + riepilogo statistico.
*/
public BatchReport analyzeBatch(List<String> passwords, List<String> users,
String dept, AnalysisOptions opts) {
List<AnalysisResult> results = new ArrayList<>();
for (int i = 0; i < passwords.size(); i++) {
String u = (users != null && i < users.size()) ? users.get(i) : "Utente_" + (i+1);
results.add(analyze(passwords.get(i), u, dept, opts));
}
return new BatchReport(dept, results);
}
public static class BatchReport {
public final String dept;
public final List<AnalysisResult> results;
public final int avgRisk;
public final int criticalCount;
public final int highCount;
public final int mediumCount;
public final int lowCount;
public BatchReport(String dept, List<AnalysisResult> results) {
this.dept = dept;
this.results = Collections.unmodifiableList(results);
this.avgRisk = results.isEmpty() ? 0
: (int) results.stream().mapToInt(r -> r.riskScore).average().orElse(0);
this.criticalCount = (int) results.stream().filter(r -> r.riskScore >= 75).count();
this.highCount = (int) results.stream().filter(r -> r.riskScore >= 50 && r.riskScore < 75).count();
this.mediumCount = (int) results.stream().filter(r -> r.riskScore >= 25 && r.riskScore < 50).count();
this.lowCount = (int) results.stream().filter(r -> r.riskScore < 25).count();
}
public String toSummary() {
return String.format(
"Reparto: %s | Totale: %d | Risk Medio: %d | CRITICI: %d | ALTI: %d | MEDI: %d | BASSI: %d",
dept, results.size(), avgRisk, criticalCount, highCount, mediumCount, lowCount);
}
}
// ── Metodi di supporto ───────────────────────────────────────────
private int computeCharspace(String pw) {
int cs = 0;
if (pw.matches(".*[a-z].*")) cs += 26;
if (pw.matches(".*[A-Z].*")) cs += 26;
if (pw.matches(".*[0-9].*")) cs += 10;
if (pw.matches(".*[^A-Za-z0-9].*")) cs += 32;
return cs;
}
// Metodo deleet rimosso: ora gestito dal checker LeetSpeakChecker
private String formatCrackTime(double seconds) {
if (seconds < 1) return "< 1 secondo";
if (seconds < 60) return String.format("%.0f secondi", seconds);
if (seconds < 3600) return String.format("%.0f minuti", seconds/60);
if (seconds < 86400) return String.format("%.1f ore", seconds/3600);
if (seconds < 86400*365) return String.format("%.0f giorni", seconds/86400);
if (seconds < 86400*365*1000) return String.format("%.1f anni", seconds/86400/365);
if (seconds < 86400*365*1e6) return String.format("%.1fk anni", seconds/86400/365/1000);
if (seconds < 86400*365*1e9) return String.format("%.1fM anni", seconds/86400/365/1_000_000);
return String.format("%.1fB+ anni", seconds/86400/365/1_000_000_000);
}
private List<String> generateRecommendations(int riskScore, double entropy, int length,
List<Finding> findings, List<String> tags,
List<String> missingCharsets) {
List<String> recs = new ArrayList<>();
if (length < 16)
recs.add("Aumenta la lunghezza a 16+ caratteri. Considera una passphrase (4 parole + simboli).");
if (missingCharsets.contains("simboli"))
recs.add("Aggiungi simboli speciali (!@#$%^&*) per ampliare il charset a 95 caratteri.");
if (missingCharsets.contains("maiuscole") || missingCharsets.contains("minuscole"))
recs.add("Usa sia maiuscole che minuscole per raddoppiare lo spazio per ogni lettera.");
if (riskScore > 60)
recs.add("Usa un password manager (Bitwarden, 1Password, KeePass) per password casuali e univoche.");
if (tags.contains("CORPORATE"))
recs.add("Elimina qualsiasi riferimento aziendale: nome, prodotti, acronimi, anno fondazione.");
if (tags.contains("DATE PATTERN"))
recs.add("Rimuovi date e anni: riducono le varianti da testare a poche migliaia.");
if (findings.stream().anyMatch(f -> f.severity == Finding.Severity.CRITICAL))
recs.add("AZIONE IMMEDIATA: cambia la password e attiva 2FA su tutti i servizi critici.");
recs.add("Policy aziendale consigliata: 16+ char, 4 charset, rotazione 180gg, no riuso ultime 10.");
if (entropy >= 60 && riskScore < 40)
recs.add("La password mostra buona entropia. Non riutilizzarla su altri servizi.");
return recs;
}
private AnalysisResult buildEmptyResult(String user, String dept) {
return new AnalysisResult(user, dept, 0, 100, 0,
"< 1 secondo", "< 1 secondo", "< 1 secondo",
Collections.emptyMap(),
Arrays.asList(new Finding(Finding.Severity.CRITICAL, "Password vuota", "Nessuna password impostata.")),
Arrays.asList("Impostare immediatamente una password complessa."),
Arrays.asList("EMPTY"));
}
// ── Demo CLI ─────────────────────────────────────────────────────
public static void main(String[] args) {
PasswordAnalysisEngine engine = new PasswordAnalysisEngine();
System.out.println("\n╔══════════════════════════════════════════╗");
System.out.println("║ LeakHunter — Password Exposure Tool ║");
System.out.println("╚══════════════════════════════════════════╝\n");
// Test singolo
AnalysisOptions opts = AnalysisOptions.defaults().withCompany("Acme Corporation, 2010, tech");
String[] testPasswords = { "Acme2024!", "P@ssw0rd", "qwerty123", "C0rr3tt0-Fr4tt0-92!", "monkey" };
String[] testUsers = { "mario.rossi", "anna.bianchi", "luca.verdi", "sara.neri", "admin" };
for (int i = 0; i < testPasswords.length; i++) {
AnalysisResult r = engine.analyze(testPasswords[i], testUsers[i], "IT", opts);
System.out.println(r.toReport());
}
// Test batch
System.out.println("\n══════════ ANALISI BATCH — Reparto Finanza ══════════\n");
List<String> batchPw = Arrays.asList("Finanza1!", "acme2023", "Rossi1990", "Xk9#mQzP2$vL!");
List<String> batchUsers = Arrays.asList("f.bianchi", "g.rossi", "m.verdi", "r.neri");
BatchReport batch = engine.analyzeBatch(batchPw, batchUsers, "Finanza", opts);
System.out.println(batch.toSummary());
batch.results.forEach(r -> System.out.printf(
" %-15s → Risk: %3d %-10s | Entropia: %5.1f bit | Crack GPU: %s%n",
r.user, r.riskScore, "[" + r.getRiskLevel() + "]", r.entropy, r.crackTimeGPU));
}
}