commit ef7473a29e91e266f8e255cee007a1c0e701b2f0 Author: Caileb Date: Wed May 28 21:42:50 2025 -0500 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..bc57c21 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,132 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rs-random" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6c91fd0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rs-random" +version = "0.1.0" +edition = "2024" + +[dependencies] +rand = { version = "0.8", default-features = false, features = ["std_rng", "getrandom"] } + +[profile.release] +opt-level = "z" # Optimize aggressively for size +lto = true # Link-time optimization +codegen-units = 1 # Maximum optimization opportunities +panic = "abort" # Smaller panic handling +strip = true # Strip symbols (Rust 1.59+) +overflow-checks = false # Disable overflow checks for size +debug = false # No debug info +debug-assertions = false # No debug assertions +incremental = false # Disable incremental compilation +rpath = false # Don't use rpath + +[profile.release.package."*"] +opt-level = "z" # Apply size optimization to all dependencies \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e4e3ab --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# rs-random + +**Secure String Generator** + +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 + +- 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 + +## About + +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 + +### From Source + +```bash +git clone https://git.caileb.com/Caileb/rs-random.git +cd rs-random +cargo build --release +``` + +## Usage + +```bash +rs-random [LENGTH] +rs-random -l [-s ] [-c ] +rs-random -h | --help +``` + +- `LENGTH` <number>: Length of each string (default: `16`) +- `-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 + +| Name | Description | +| ---------------- | ---------------------------------------- | +| lowercase | English lowercase letters (a-z) | +| uppercase | English uppercase letters (A-Z) | +| numbers | Numbers (0-9) | +| special | Special characters | +| special-safe | Safe special chars (no pipes/brackets) | +| cyrillic-lower | Cyrillic lowercase letters | +| cyrillic-upper | Cyrillic uppercase letters | +| greek-lower | Greek lowercase letters | +| greek-upper | Greek uppercase letters | +| symbols | Extended symbols (©®, ™, €, £, etc.) | + +### Examples + +Generate a single 32-character string (default sets): + +```bash +rs-random 32 +``` + +Generate five 12-character strings using only lowercase and numbers: + +```bash +rs-random -l 12 -s lowercase,numbers -c 5 +``` diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5c1e3b2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,157 @@ +use rand::{rngs::OsRng, seq::SliceRandom}; +use std::collections::HashSet; +use std::env; +use std::process; + +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)"), + ("special", "!@#$%^&*()_+-=[]{}|;:,.<>?", "Special characters"), + ("special-safe", "!@#$%^&*_+-=<>?", "Safe special chars (no pipes/brackets)"), + ("cyrillic-lower", "абвгдежзийклмнопрстуфхцчшщъыьэюя", "Cyrillic lowercase"), + ("cyrillic-upper", "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "Cyrillic uppercase"), + ("greek-lower", "αβγδεζηθικλμνξοπρστυφχψω", "Greek lowercase"), + ("greek-upper", "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ", "Greek uppercase"), + ("symbols", "©®™€£¥§¶†‡•…‰′″‹›\"\'–—", "Extended symbols"), +]; + +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 | --help\n"); + println!("Available sets (for -s, comma-separated):"); + for &(name, _, desc) in &CHARACTER_SETS { + println!(" {:<15} - {}", name, desc); + } +} + +fn get_chars(selected_sets: &[&str]) -> Result, String> { + let mut all_chars = Vec::new(); + let mut unknown = Vec::new(); + + for &set_name in selected_sets { + match CHARACTER_SETS.iter().find(|&&(name, _, _)| name == set_name) { + Some(&(_, chars, _)) => all_chars.extend(chars.chars()), + None => unknown.push(set_name), + } + } + + if !unknown.is_empty() { + return Err(format!("Unknown sets: {}", unknown.join(","))); + } + if all_chars.is_empty() { + return Err("Empty character pool".to_string()); + } + + // 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); + } + } + + Ok(unique_chars) +} + +fn generate_random_string(chars: &[char], length: usize) -> String { + let mut rng = OsRng; + (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()) || args.contains(&"--help".to_string()) { + print_help(); + return; + } + + let mut length = 16; + let mut count = 1; + let mut sets = "lowercase,uppercase,numbers,special-safe"; + + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "-l" | "--length" => { + if i + 1 < args.len() { + if let Ok(n) = args[i + 1].parse::() { + length = n; + } else { + eprintln!("Error: Invalid length"); + process::exit(1); + } + i += 2; + } else { + eprintln!("Error: Missing length value"); + process::exit(1); + } + } + "-s" | "--sets" => { + if i + 1 < args.len() { + sets = &args[i + 1]; + i += 2; + } else { + eprintln!("Error: Missing sets value"); + process::exit(1); + } + } + "-c" | "--count" => { + if i + 1 < args.len() { + if let Ok(n) = args[i + 1].parse::() { + count = n; + } else { + eprintln!("Error: Invalid count"); + process::exit(1); + } + i += 2; + } else { + eprintln!("Error: Missing count value"); + 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; + } + _ => { + i += 1; + } + } + } + + if length == 0 { + eprintln!("Error: Length must be > 0"); + process::exit(1); + } + + let selected_sets: Vec<&str> = sets.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect(); + + match get_chars(&selected_sets) { + Ok(chars) => { + for i in 0..count { + let random_string = generate_random_string(&chars, length); + if count > 1 { + print!("{}: ", i + 1); + } + println!("{}", random_string); + } + } + Err(e) => { + eprintln!("Error: {}", e); + process::exit(1); + } + } +} \ No newline at end of file