diff --git a/Cargo.lock b/Cargo.lock index e327ee9..3395bf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,7 +83,7 @@ dependencies = [ [[package]] name = "rs-random" -version = "0.2.1" +version = "0.1.1" dependencies = [ "rand", ] diff --git a/Cargo.toml b/Cargo.toml index c19fe84..e4d11da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rs-random" -version = "0.2.1" +version = "0.1.1" edition = "2024" [dependencies] diff --git a/README.md b/README.md index 739587a..3045164 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,18 @@ **Secure String Generator** -A blazing-fast Rust command-line tool for generating secure random strings with the tiniest possible footprint. +A minimal, blazing-fast Rust command-line tool for generating secure random strings with the tiniest possible footprint. Perfect for standalone use or integration into other applications. ## Features -- **True cryptographic security**: Uses operating system entropy (`OsRng`) for cryptographically secure randomness -- **Flexible character sets**: Includes alphabets, numbers, symbols, and more -- **Tiny binary size**: Aggressively optimized for size -- **Zero dependencies**: Only uses Rust's `rand` crate with minimal features +- Uses operating system entropy (`OsRng`) for cryptographically secure randomness +- Flexible selection of character sets, including alphabets, numbers, symbols, and more +- Very small binary size +- Minimal dependencies -## Security +## About -`rs-random` uses `OsRng` from Rust's `rand` crate, which is cryptographically secure and implements the `CryptoRng` trait. It pulls entropy directly from: -- Linux/Unix: `/dev/urandom` (via `getrandom` syscall when available) -- Windows: `CryptGenRandom` -- macOS: `SecRandomCopyBytes` - -This is more secure than OpenSSL's `RAND_bytes()` which uses a userland PRNG. +I created `rs-random` to have a super simple, minimal way to generate secure strings that I could build into other applications, ensuring security, speed, and a tiny footprint. ## Installation @@ -48,17 +43,6 @@ rs-random -h - `-c `: Number of strings to generate (default: `1`) - `-h`: Show help information -### Default Character Sets - -When you run `rs-random` without specifying sets, it uses: **lowercase + uppercase + numbers + special-safe** - -This gives you a secure password with: -- 26 lowercase letters (a-z) -- 26 uppercase letters (A-Z) -- 10 numbers (0-9) -- 15 safe special characters: `!@#$%^&*_+-=<>?` -- **Total: 77 possible characters per position** - ### Available Character Sets | Name | Description | @@ -66,7 +50,6 @@ This gives you a secure password with: | lowercase | English lowercase letters (a-z) | | uppercase | English uppercase letters (A-Z) | | numbers | Numbers (0-9) | -| hex | Hexadecimal (0-9, a-f) | | special | Special characters | | special-safe | Safe special chars (no pipes/brackets) | | cyrillic-lower | Cyrillic lowercase letters | @@ -83,14 +66,8 @@ Generate a single 32-character string (default sets): rs-random 32 ``` -Generate a 32-character hex string (like `openssl rand -hex 16`): - -```bash -rs-random -l 32 -s hex -``` - Generate five 12-character strings using only lowercase and numbers: ```bash rs-random -l 12 -s lowercase,numbers -c 5 -``` \ No newline at end of file +``` diff --git a/src/main.rs b/src/main.rs index fa8f63e..50a2bbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,11 +3,10 @@ use std::collections::HashSet; use std::env; use std::process; -static CHARACTER_SETS: [(&str, &str, &str); 11] = [ +static CHARACTER_SETS: [(&str, &str, &str); 10] = [ ("lowercase", "abcdefghijklmnopqrstuvwxyz", "English lowercase letters (a-z)"), ("uppercase", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "English uppercase letters (A-Z)"), ("numbers", "0123456789", "Numbers (0-9)"), - ("hex", "0123456789abcdef", "Hexadecimal (0-9, a-f)"), ("special", "!@#$%^&*()_+-=[]{}|;:,.<>?", "Special characters"), ("special-safe", "!@#$%^&*_+-=<>?", "Safe special chars (no pipes/brackets)"), ("cyrillic-lower", "абвгдежзийклмнопрстуфхцчшщъыьэюя", "Cyrillic lowercase"), @@ -17,7 +16,6 @@ static CHARACTER_SETS: [(&str, &str, &str); 11] = [ ("symbols", "©®™€£¥§¶†‡•…‰′″‹›\"\'–—", "Extended symbols"), ]; -#[inline(never)] fn print_help() { println!("rs-random: Secure String Generator\n"); println!("Usage:"); @@ -30,10 +28,8 @@ fn print_help() { } } -#[inline] fn get_chars(selected_sets: &[&str]) -> Result, String> { - // Pre-allocate with estimated capacity - let mut all_chars = Vec::with_capacity(selected_sets.len() * 32); + let mut all_chars = Vec::new(); let mut unknown = Vec::new(); for &set_name in selected_sets { @@ -50,49 +46,23 @@ fn get_chars(selected_sets: &[&str]) -> Result, String> { return Err("Empty character pool".to_string()); } - // Optimize for common case where no duplicates exist - if selected_sets.len() == 1 || - (selected_sets.len() <= 4 && !selected_sets.iter().any(|&s| - s == "numbers" && (selected_sets.contains(&"hex") || - selected_sets.contains(&"lowercase") || - selected_sets.contains(&"uppercase")))) { - return Ok(all_chars); - } - - // Remove duplicates only when necessary - let mut unique_chars = Vec::with_capacity(all_chars.len()); - let mut seen = HashSet::with_capacity(all_chars.len()); + // Remove duplicates + let mut unique_chars = Vec::new(); + let mut seen = HashSet::new(); for ch in all_chars { if seen.insert(ch) { unique_chars.push(ch); } } - unique_chars.shrink_to_fit(); Ok(unique_chars) } -#[inline] fn generate_random_string(chars: &[char], length: usize) -> String { let mut rng = OsRng; - let mut result = String::with_capacity(length); - - // Unroll loop for better performance on small strings - let chunks = length / 4; - let remainder = length % 4; - - for _ in 0..chunks { - result.push(*chars.choose(&mut rng).unwrap()); - result.push(*chars.choose(&mut rng).unwrap()); - result.push(*chars.choose(&mut rng).unwrap()); - result.push(*chars.choose(&mut rng).unwrap()); - } - - for _ in 0..remainder { - result.push(*chars.choose(&mut rng).unwrap()); - } - - result + (0..length) + .map(|_| *chars.choose(&mut rng).unwrap()) + .collect() } fn main() { @@ -112,12 +82,11 @@ fn main() { match args[i].as_str() { "-l" => { if i + 1 < args.len() { - match args[i + 1].parse::() { - Ok(n) => length = n, - Err(_) => { - eprintln!("Error: Invalid length"); - process::exit(1); - } + if let Ok(n) = args[i + 1].parse::() { + length = n; + } else { + eprintln!("Error: Invalid length"); + process::exit(1); } i += 2; } else { @@ -136,12 +105,11 @@ fn main() { } "-c" => { if i + 1 < args.len() { - match args[i + 1].parse::() { - Ok(n) => count = n, - Err(_) => { - eprintln!("Error: Invalid count"); - process::exit(1); - } + if let Ok(n) = args[i + 1].parse::() { + count = n; + } else { + eprintln!("Error: Invalid count"); + process::exit(1); } i += 2; } else { @@ -149,13 +117,12 @@ fn main() { process::exit(1); } } - arg if i == 1 && !arg.starts_with('-') => { - match arg.parse::() { - Ok(n) => length = n, - Err(_) => { - eprintln!("Error: Invalid length"); - process::exit(1); - } + arg if i == 1 && !arg.starts_with("-") => { + if let Ok(n) = arg.parse::() { + length = n; + } else { + eprintln!("Error: Invalid length"); + process::exit(1); } i += 1; }