diff --git a/Cargo.lock b/Cargo.lock index e327ee9..bc57c21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,7 +83,7 @@ dependencies = [ [[package]] name = "rs-random" -version = "0.2.1" +version = "0.1.0" dependencies = [ "rand", ] diff --git a/Cargo.toml b/Cargo.toml index c19fe84..6c91fd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rs-random" -version = "0.2.1" +version = "0.1.0" edition = "2024" [dependencies] diff --git a/README.md b/README.md index 739587a..7e4e3ab 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,21 @@ **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 +- Very small binary size (optimized release profile) +- Minimal dependencies (only the `rand` crate with `std_rng` and `getrandom` features) +- Flexible selection of character sets, including alphabets, numbers, symbols, and more -## 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 -### Windows - -Download the latest Windows executable from the [releases page](https://git.caileb.com/Caileb/rs-random/releases). - ### From Source ```bash @@ -39,25 +30,14 @@ cargo build --release ```bash rs-random [LENGTH] rs-random -l [-s ] [-c ] -rs-random -h +rs-random -h | --help ``` - `LENGTH` <number>: Length of each string (default: `16`) -- `-l `: Specify the string length -- `-s `: Comma-separated list of character sets to include (default: `lowercase,uppercase,numbers,special-safe`) -- `-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** +- `-l, --length `: Specify the string length +- `-s, --sets `: Comma-separated list of character sets to include (default: `lowercase,uppercase,numbers,special-safe`) +- `-c, --count `: Number of strings to generate (default: `1`) +- `-h, --help`: Show help information ### Available Character Sets @@ -66,7 +46,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 +62,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..5c1e3b2 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,23 +16,20 @@ static CHARACTER_SETS: [(&str, &str, &str); 11] = [ ("symbols", "©®™€£¥§¶†‡•…‰′″‹›\"\'–—", "Extended symbols"), ]; -#[inline(never)] fn print_help() { println!("rs-random: Secure String Generator\n"); println!("Usage:"); println!(" rs-random [LENGTH] (uses safe defaults)"); println!(" rs-random -l [-s ] [-c ]"); - println!(" rs-random -h\n"); + println!(" rs-random -h | --help\n"); println!("Available sets (for -s, comma-separated):"); for &(name, _, desc) in &CHARACTER_SETS { println!(" {:<15} - {}", name, desc); } } -#[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,55 +46,29 @@ 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() { let args: Vec = env::args().collect(); - if args.len() < 2 || args.contains(&"-h".to_string()) { + if args.len() < 2 || args.contains(&"-h".to_string()) || args.contains(&"--help".to_string()) { print_help(); return; } @@ -110,14 +80,13 @@ fn main() { let mut i = 1; while i < args.len() { match args[i].as_str() { - "-l" => { + "-l" | "--length" => { 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 { @@ -125,7 +94,7 @@ fn main() { process::exit(1); } } - "-s" => { + "-s" | "--sets" => { if i + 1 < args.len() { sets = &args[i + 1]; i += 2; @@ -134,14 +103,13 @@ fn main() { process::exit(1); } } - "-c" => { + "-c" | "--count" => { 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; }