Skip to content

Commit f09c168

Browse files
committed
Merge branch 'dev' for release 1.2.0
2 parents 4c826ee + 546b0db commit f09c168

File tree

10 files changed

+1106
-89
lines changed

10 files changed

+1106
-89
lines changed

Cargo.toml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
[package]
22
name = "worgen_x"
3-
version = "1.1.1"
3+
version = "1.2.0"
44
edition = "2021"
55
authors = ["Xen0rInspire"]
66
license = "GNU General Public License v3.0"
7+
repository = "https://github.com/XenorInspire/WorgenX"
8+
homepage = "https://github.com/XenorInspire/WorgenX"
9+
keywords = ["password", "cracking", "wordlist", "entropy", "random", "generator"]
10+
readme = "README.md"
711
description = "A powerful command line tool to generate relevant wordlists for password cracking. It can also generate random passwords with a good entropy."
812

913
[features]
1014
cli = ["serde_json", "clap"]
1115
gui = []
1216

1317
[dependencies]
14-
rand = "0.8.5"
15-
thiserror = "1.0.59"
18+
rand = { version = "0.8.5", features = ["getrandom"], default-features = false }
19+
thiserror = "1.0.61"
1620
num_cpus = "1.16.0"
17-
serde_json = { version = "1.0.116", optional = true }
18-
indicatif = "0.17.8"
19-
clap = { version = "4.5.4", optional = true }
21+
serde_json = { version = "1.0.120", optional = true, features = ["std"], default-features = false }
22+
indicatif = { version = "0.17.8", default-features = false }
23+
clap = { version = "4.5.9", optional = true, features = ["std"], default-features = false }
24+
md-5 = "0.10.6"
25+
sha-1 = "0.10.1"
26+
sha2 = "0.10.8"
27+
digest = "0.10.7"
28+
hex = "0.4.3"
29+
sha3 = "0.10.8"
30+
blake2 = "0.10.6"
31+
whirlpool = "0.10.4"

src/benchmark.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,14 @@ fn build_wordlist_progress_bar(seconds: u64, pb: &Arc<Mutex<ProgressBar>>) {
166166
}
167167
}
168168
}
169+
170+
#[cfg(test)]
171+
mod tests {
172+
use super::*;
173+
174+
#[test]
175+
fn test_load_cpu_benchmark() {
176+
let result: Result<u64, WorgenXError> = load_cpu_benchmark(4);
177+
assert!(result.is_ok());
178+
}
179+
}

src/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub enum ArgError {
2020
#[error("Error: missing value for {0}")]
2121
MissingValue(String),
2222
/// This error is raised if there isn't any configuration given by the user (for example just wordlist feature without any type of characters specified).
23-
#[error("Error: no configuration given for argument. Please specify the mandatory parameters and at least one type of characters.\nUsage: worgenX <command> [options]\nTry 'worgenX --help' for more information.")]
23+
#[error("Error: no configuration given for argument.\nPlease specify the mandatory parameters and at least one type of characters.\nUsage: worgenX <command> [options]\nTry 'worgenX --help' for more information.")]
2424
MissingConfiguration,
2525
}
2626

@@ -52,4 +52,7 @@ pub enum SystemError {
5252
/// This error is raised if there is a thread error.
5353
#[error("Error: thread error\n{0}")]
5454
ThreadError(String),
55+
/// This error is raised if the hash algorithm is not supported.
56+
#[error("Error: the hash algorithm `{0}` is not supported")]
57+
UnsupportedHashAlgorithm(String),
5558
}

src/json.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,36 @@ pub fn password_config_to_json(
3131
})
3232
.to_string()
3333
}
34+
35+
#[cfg(test)]
36+
mod tests {
37+
use super::*;
38+
use serde_json::Value;
39+
40+
#[test]
41+
fn test_password_config_to_json() {
42+
let password_config: PasswordConfig = PasswordConfig {
43+
numbers: true,
44+
special_characters: true,
45+
uppercase: true,
46+
lowercase: true,
47+
length: 10,
48+
number_of_passwords: 1,
49+
};
50+
let passwords: Vec<String> = vec!["password".to_string()];
51+
let json_output: String = password_config_to_json(&password_config, &passwords);
52+
53+
let json_from_str: Value = serde_json::from_str(&json_output).unwrap();
54+
let json_expected_object: Value = json!({
55+
"number_of_passwords": 1,
56+
"password_length": 10,
57+
"uppercase": true,
58+
"lowercase": true,
59+
"numbers": true,
60+
"special_characters": true,
61+
"passwords": ["password"]
62+
});
63+
64+
assert_eq!(json_from_str, json_expected_object);
65+
}
66+
}

src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Prevents the use of unsafe code
2+
#![forbid(unsafe_code)]
3+
4+
// Prevents the compilation of both modes which may cause conflicts
15
#[cfg(all(not(feature = "gui"), not(feature = "cli")))]
26
compile_error!("You must specify a mode: 'gui' or 'cli'.");
37

src/mode/cli.rs

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,20 @@ fn build_command_context() -> Command {
103103
.help("Disable the loading bar when generating the wordlist")
104104
.action(ArgAction::SetTrue),
105105
)
106+
.arg(
107+
Arg::new("hash")
108+
.short('h')
109+
.long("hash")
110+
.help("Hash algorithm to use for the wordlist")
111+
.value_parser(vec!["md5", "sha1", "sha224", "sha256", "sha384", "sha512", "sha3-224", "sha3-256", "sha3-384", "sha3-512", "blake2b", "blake2s", "whirlpool"])
112+
.value_name("hash"),
113+
)
106114
.arg(
107115
Arg::new("threads_wordlist")
108116
.short('t')
109117
.long("threads")
110118
.help("Number of threads to generate the passwords")
111-
.value_parser(value_parser!(u8))
119+
.value_parser(value_parser!(u8).range(1..u8::MAX as i64))
112120
.value_name("threads")
113121
.default_value(default_threads),
114122
);
@@ -148,7 +156,7 @@ fn build_command_context() -> Command {
148156
.short('s')
149157
.long("size")
150158
.help("Size of the passwords in characters")
151-
.value_parser(value_parser!(u32))
159+
.value_parser(value_parser!(u32).range(1..u32::MAX as i64))
152160
.value_name("size")
153161
.required(true),
154162
)
@@ -157,7 +165,7 @@ fn build_command_context() -> Command {
157165
.short('c')
158166
.long("count")
159167
.help("Number of passwords to generate")
160-
.value_parser(value_parser!(u64))
168+
.value_parser(value_parser!(u64).range(1..u64::MAX))
161169
.value_name("count")
162170
.required(true),
163171
)
@@ -193,7 +201,7 @@ fn build_command_context() -> Command {
193201
.short('t')
194202
.long("threads")
195203
.help("Number of threads to use for the CPU benchmark")
196-
.value_parser(value_parser!(u8))
204+
.value_parser(value_parser!(u8).range(1..u8::MAX as i64))
197205
.value_name("threads")
198206
.default_value(default_threads),
199207
);
@@ -234,21 +242,10 @@ pub fn run() -> Result<(), WorgenXError> {
234242

235243
command_context.build();
236244
match command_context.get_matches().subcommand() {
237-
Some(("wordlist", sub_matches)) => match run_wordlist(sub_matches) {
238-
Ok(_) => Ok(()),
239-
Err(e) => Err(e),
240-
},
241-
Some(("password", sub_matches)) => match run_passwd(sub_matches) {
242-
Ok(_) => Ok(()),
243-
Err(e) => Err(e),
244-
},
245-
Some(("benchmark", sub_matches)) => match run_benchmark(sub_matches) {
246-
Ok(_) => Ok(()),
247-
Err(e) => Err(e),
248-
},
249-
_ => {
250-
Err(WorgenXError::ArgError(ArgError::NoArgument)) // Should never happen
251-
}
245+
Some(("wordlist", sub_matches)) => run_wordlist(sub_matches),
246+
Some(("password", sub_matches)) => run_passwd(sub_matches),
247+
Some(("benchmark", sub_matches)) => run_benchmark(sub_matches),
248+
_ => Err(WorgenXError::ArgError(ArgError::NoArgument)) // Should never happen
252249
}
253250
}
254251

@@ -377,6 +374,7 @@ fn run_wordlist(sub_matches: &ArgMatches) -> Result<(), WorgenXError> {
377374
let wordlist_config: WordlistConfig = wordlist::build_wordlist_config(&wordlist_generation_parameters.wordlist_values);
378375
let nb_of_passwords: u64 = wordlist_config.dict.len().pow(wordlist_config.mask_indexes.len() as u32) as u64;
379376
println!("Estimated size of the wordlist: {}", system::get_estimated_size(nb_of_passwords, wordlist_config.formated_mask.len() as u64));
377+
println!("Wordlist generation in progress...");
380378

381379
wordlist::wordlist_generation_scheduler(
382380
&wordlist_config,
@@ -410,13 +408,15 @@ fn allocate_wordlist_config_cli(
410408
uppercase: false,
411409
lowercase: false,
412410
mask: String::new(),
411+
hash: String::new(),
413412
};
414413

415414
update_config(&mut wordlist_values.lowercase, sub_matches, "lowercase_wordlist");
416415
update_config(&mut wordlist_values.uppercase, sub_matches, "uppercase_wordlist");
417416
update_config(&mut wordlist_values.numbers, sub_matches, "numbers_wordlist");
418417
update_config(&mut wordlist_values.special_characters, sub_matches, "special_characters_wordlist");
419418
update_config(&mut wordlist_values.mask, sub_matches, "mask");
419+
update_config(&mut wordlist_values.hash, sub_matches, "hash");
420420
update_config(&mut output_file, sub_matches, "output");
421421
update_config(&mut no_loading_bar, sub_matches, "disable_loading_bar");
422422
update_config(&mut threads, sub_matches, "threads_wordlist");
@@ -543,6 +543,7 @@ fn display_help() {
543543
println!(" -o <path>, --output <path>\t\tSave the wordlist in a text file");
544544
println!("\n The following options are optional:");
545545
println!(" -d, --disable-loading-bar\t\tDisable the loading bar when generating the wordlist");
546+
println!(" -h, --hash <hash>\t\t\tHash algorithm to use for the wordlist.\n\t\t\t\t\tYou can choose between: md5, sha1, sha224, sha256, sha384, sha512,\n\t\t\t\t\tsha3-224, sha3-256, sha3-384, sha3-512, blake2b-512, blake2s-256 and whirlpool");
546547
println!(" -t <threads>, --threads <threads>\tNumber of threads to generate the passwords\n\t\t\t\t\tBy default, the number of threads is based on the number of physical cores of the CPU");
547548

548549
println!("\n --- Password generation ---");
@@ -563,3 +564,71 @@ fn display_help() {
563564
println!(" The following option is optional:");
564565
println!(" -t <threads>, --threads <threads>\tNumber of threads to use for the CPU benchmark\n\t\t\t\t\tBy default, the number of threads is based on the number of physical cores of the CPU\n");
565566
}
567+
568+
#[cfg(test)]
569+
mod tests {
570+
use super::*;
571+
572+
#[test]
573+
fn test_allocate_passwd_config_cli() {
574+
let command_context: Command = build_command_context();
575+
let matches: ArgMatches = command_context.get_matches_from(vec!["worgenX", "password", "-l", "-u", "-n", "-x", "-s", "10", "-c", "5", "-o", "test.txt", "-j"]);
576+
577+
match matches.subcommand(){
578+
Some(("password", sub_matches)) => {
579+
let result: PasswordGenerationOptions = allocate_passwd_config_cli(sub_matches).unwrap();
580+
assert_eq!(result.password_config.number_of_passwords, 5);
581+
assert_eq!(result.password_config.length, 10);
582+
assert!(result.password_config.lowercase);
583+
assert!(result.password_config.uppercase);
584+
assert!(result.password_config.numbers);
585+
assert!(result.password_config.special_characters);
586+
assert!(result.output_file.contains("test.txt"));
587+
assert!(result.json);
588+
assert!(!result.no_display);
589+
},
590+
_ => panic!("Should never happen")
591+
}
592+
}
593+
594+
#[test]
595+
fn test_allocate_wordlist_config_cli() {
596+
let command_context: Command = build_command_context();
597+
let matches: ArgMatches = command_context.get_matches_from(vec!["worgenX", "wordlist", "-l", "-u", "-n", "-x", "-m", "A?1", "-o", "test.txt", "-d", "-t", "4"]);
598+
599+
match matches.subcommand(){
600+
Some(("wordlist", sub_matches)) => {
601+
let result: WordlistGenerationOptions = allocate_wordlist_config_cli(sub_matches).unwrap();
602+
assert_eq!(result.wordlist_values.mask, "A?1");
603+
assert_eq!(result.threads, 4);
604+
assert!(result.wordlist_values.lowercase);
605+
assert!(result.wordlist_values.uppercase);
606+
assert!(result.wordlist_values.numbers);
607+
assert!(result.wordlist_values.special_characters);
608+
assert!(result.output_file.contains("test.txt"));
609+
assert!(result.no_loading_bar);
610+
},
611+
_ => panic!("Should never happen")
612+
}
613+
}
614+
615+
#[test]
616+
fn test_allocate_benchmark_config_cli() {
617+
let command_context: Command = build_command_context();
618+
let matches: ArgMatches = command_context.get_matches_from(vec!["worgenX", "benchmark", "-t", "4"]);
619+
620+
match matches.subcommand(){
621+
Some(("benchmark", sub_matches)) => {
622+
let result: BenchmarkOptions = allocate_benchmark_config_cli(sub_matches).unwrap();
623+
assert_eq!(result.threads, 4);
624+
},
625+
_ => panic!("Should never happen")
626+
}
627+
}
628+
629+
#[test]
630+
fn test_check_output_arg() {
631+
let result: Result<String, WorgenXError> = check_output_arg("./test.txt");
632+
assert!(result.is_ok());
633+
}
634+
}

src/mode/gui.rs

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use std::{
2626
pub fn run() {
2727
loop {
2828
print_menu();
29-
let choice = system::get_user_choice();
29+
let choice: String = system::get_user_choice();
3030
match &*choice {
3131
"0" => break,
3232
"1" => main_wordlist_generation(),
@@ -211,6 +211,7 @@ fn main_wordlist_generation() {
211211
file_result = saving_procedure(target::WORDLISTS_FOLDER);
212212
}
213213

214+
println!("Wordlist generation started.");
214215
let (_, filename) = file_result.unwrap();
215216
if let Err(e) = wordlist::wordlist_generation_scheduler(
216217
&wordlist_config,
@@ -243,6 +244,7 @@ fn allocate_wordlist_config_gui() -> WordlistValues {
243244
uppercase: false,
244245
lowercase: false,
245246
mask: String::new(),
247+
hash: String::new(),
246248
};
247249
let mut is_option_chosen: bool = false;
248250

@@ -277,6 +279,11 @@ fn allocate_wordlist_config_gui() -> WordlistValues {
277279
}
278280
}
279281

282+
println!("Do you want to hash the passwords of the wordlist ? (y/n)");
283+
if system::get_user_choice_yn().eq("y") {
284+
wordlist_config.hash = get_hash_choice();
285+
}
286+
280287
println!("\nEnter the mask of the wordlist :");
281288
println!("For every character you want to be fixed, enter the character itself.");
282289
println!("For every character you want to be variable, enter a ?.");
@@ -292,10 +299,7 @@ fn allocate_wordlist_config_gui() -> WordlistValues {
292299
println!("The mask must contain at least one '?' !");
293300
continue;
294301
} else {
295-
println!(
296-
"Do you want to validate the following mask : '{}' ? (y/n)",
297-
wordlist_config.mask
298-
);
302+
println!("Do you want to validate the following mask : '{}' ? (y/n)", wordlist_config.mask);
299303
if system::get_user_choice_yn().eq("y") {
300304
is_valid_mask = true;
301305
}
@@ -316,10 +320,7 @@ fn main_benchmark() {
316320
thread::sleep(std::time::Duration::from_secs(5));
317321
match benchmark::load_cpu_benchmark(num_cpus::get_physical() as u8) {
318322
Ok(nb_of_passwords) => {
319-
println!(
320-
"Your CPU has generated {} passwords in 1 minute",
321-
nb_of_passwords
322-
);
323+
println!("Your CPU has generated {} passwords in 1 minute", nb_of_passwords);
323324
}
324325
Err(e) => {
325326
println!("{}", e);
@@ -386,3 +387,40 @@ pub fn saving_procedure(target: &str) -> Result<(File, String), SystemError> {
386387

387388
Ok((file, filename))
388389
}
390+
391+
/// This function is charged to get the hash choice from the user.
392+
///
393+
/// # Returns
394+
///
395+
/// The hash choice as a string. It returns an empty string if the user does not want to hash the passwords anymore.
396+
///
397+
fn get_hash_choice() -> String {
398+
let hash_choices: [&str; 14] = [
399+
"md5",
400+
"sha1",
401+
"sha224",
402+
"sha256",
403+
"sha384",
404+
"sha512",
405+
"sha3-224",
406+
"sha3-256",
407+
"sha3-384",
408+
"sha3-512",
409+
"blake2b-512",
410+
"blake2s-256",
411+
"whirlpool",
412+
"",
413+
];
414+
415+
loop {
416+
println!("Choose the hash algorithm you want to use :");
417+
for (i, hash) in hash_choices.iter().enumerate() {
418+
println!("{} : {}", i + 1, if hash.is_empty() { "None" } else { hash });
419+
}
420+
421+
match system::get_user_choice().trim().parse::<usize>() {
422+
Ok(n) if n >= 1 && n <= hash_choices.len() => return hash_choices[n - 1].to_string(),
423+
_ => println!("Error: please specify a valid option"),
424+
}
425+
}
426+
}

0 commit comments

Comments
 (0)