Added hex, speed improvements, size reduction

This commit is contained in:
Caileb 2025-05-28 22:30:59 -05:00
parent ef7473a29e
commit 2558b97e0e
4 changed files with 104 additions and 44 deletions

2
Cargo.lock generated
View file

@ -83,7 +83,7 @@ dependencies = [
[[package]] [[package]]
name = "rs-random" name = "rs-random"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"rand", "rand",
] ]

View file

@ -1,6 +1,6 @@
[package] [package]
name = "rs-random" name = "rs-random"
version = "0.1.0" version = "0.1.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View file

@ -2,21 +2,30 @@
**Secure String Generator** **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. A blazing-fast Rust command-line tool for generating secure random strings with the tiniest possible footprint.
## Features ## Features
- Uses operating system entropy (`OsRng`) for cryptographically secure randomness - **True cryptographic security**: Uses operating system entropy (`OsRng`) for cryptographically secure randomness
- Very small binary size (optimized release profile) - **Flexible character sets**: Includes alphabets, numbers, symbols, and more
- Minimal dependencies (only the `rand` crate with `std_rng` and `getrandom` features) - **Tiny binary size**: Aggressively optimized for size
- Flexible selection of character sets, including alphabets, numbers, symbols, and more - **Zero dependencies**: Only uses Rust's `rand` crate with minimal features
## About ## Security
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. `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.
## Installation ## Installation
### Windows
Download the latest Windows executable from the [releases page](https://git.caileb.com/Caileb/rs-random/releases).
### From Source ### From Source
```bash ```bash
@ -30,14 +39,25 @@ cargo build --release
```bash ```bash
rs-random [LENGTH] rs-random [LENGTH]
rs-random -l <LENGTH> [-s <SETS>] [-c <COUNT>] rs-random -l <LENGTH> [-s <SETS>] [-c <COUNT>]
rs-random -h | --help rs-random -h
``` ```
- `LENGTH` &lt;number&gt;: Length of each string (default: `16`) - `LENGTH` &lt;number&gt;: Length of each string (default: `16`)
- `-l, --length <LENGTH>`: Specify the string length - `-l <LENGTH>`: Specify the string length
- `-s, --sets <SETS>`: Comma-separated list of character sets to include (default: `lowercase,uppercase,numbers,special-safe`) - `-s <SETS>`: Comma-separated list of character sets to include (default: `lowercase,uppercase,numbers,special-safe`)
- `-c, --count <COUNT>`: Number of strings to generate (default: `1`) - `-c <COUNT>`: Number of strings to generate (default: `1`)
- `-h, --help`: Show help information - `-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 ### Available Character Sets
@ -46,6 +66,7 @@ rs-random -h | --help
| lowercase | English lowercase letters (a-z) | | lowercase | English lowercase letters (a-z) |
| uppercase | English uppercase letters (A-Z) | | uppercase | English uppercase letters (A-Z) |
| numbers | Numbers (0-9) | | numbers | Numbers (0-9) |
| hex | Hexadecimal (0-9, a-f) |
| special | Special characters | | special | Special characters |
| special-safe | Safe special chars (no pipes/brackets) | | special-safe | Safe special chars (no pipes/brackets) |
| cyrillic-lower | Cyrillic lowercase letters | | cyrillic-lower | Cyrillic lowercase letters |
@ -62,6 +83,12 @@ Generate a single 32-character string (default sets):
rs-random 32 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: Generate five 12-character strings using only lowercase and numbers:
```bash ```bash

View file

@ -3,10 +3,11 @@ use std::collections::HashSet;
use std::env; use std::env;
use std::process; use std::process;
static CHARACTER_SETS: [(&str, &str, &str); 10] = [ static CHARACTER_SETS: [(&str, &str, &str); 11] = [
("lowercase", "abcdefghijklmnopqrstuvwxyz", "English lowercase letters (a-z)"), ("lowercase", "abcdefghijklmnopqrstuvwxyz", "English lowercase letters (a-z)"),
("uppercase", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "English uppercase letters (A-Z)"), ("uppercase", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "English uppercase letters (A-Z)"),
("numbers", "0123456789", "Numbers (0-9)"), ("numbers", "0123456789", "Numbers (0-9)"),
("hex", "0123456789abcdef", "Hexadecimal (0-9, a-f)"),
("special", "!@#$%^&*()_+-=[]{}|;:,.<>?", "Special characters"), ("special", "!@#$%^&*()_+-=[]{}|;:,.<>?", "Special characters"),
("special-safe", "!@#$%^&*_+-=<>?", "Safe special chars (no pipes/brackets)"), ("special-safe", "!@#$%^&*_+-=<>?", "Safe special chars (no pipes/brackets)"),
("cyrillic-lower", "абвгдежзийклмнопрстуфхцчшщъыьэюя", "Cyrillic lowercase"), ("cyrillic-lower", "абвгдежзийклмнопрстуфхцчшщъыьэюя", "Cyrillic lowercase"),
@ -16,20 +17,23 @@ static CHARACTER_SETS: [(&str, &str, &str); 10] = [
("symbols", "©®™€£¥§¶†‡•…‰′″‹›\"\'–—", "Extended symbols"), ("symbols", "©®™€£¥§¶†‡•…‰′″‹›\"\'–—", "Extended symbols"),
]; ];
#[inline(never)]
fn print_help() { fn print_help() {
println!("rs-random: Secure String Generator\n"); println!("rs-random: Secure String Generator\n");
println!("Usage:"); println!("Usage:");
println!(" rs-random [LENGTH] (uses safe defaults)"); println!(" rs-random [LENGTH] (uses safe defaults)");
println!(" rs-random -l <LEN> [-s <SETS>] [-c <COUNT>]"); println!(" rs-random -l <LEN> [-s <SETS>] [-c <COUNT>]");
println!(" rs-random -h | --help\n"); println!(" rs-random -h\n");
println!("Available sets (for -s, comma-separated):"); println!("Available sets (for -s, comma-separated):");
for &(name, _, desc) in &CHARACTER_SETS { for &(name, _, desc) in &CHARACTER_SETS {
println!(" {:<15} - {}", name, desc); println!(" {:<15} - {}", name, desc);
} }
} }
#[inline]
fn get_chars(selected_sets: &[&str]) -> Result<Vec<char>, String> { fn get_chars(selected_sets: &[&str]) -> Result<Vec<char>, String> {
let mut all_chars = Vec::new(); // Pre-allocate with estimated capacity
let mut all_chars = Vec::with_capacity(selected_sets.len() * 32);
let mut unknown = Vec::new(); let mut unknown = Vec::new();
for &set_name in selected_sets { for &set_name in selected_sets {
@ -46,29 +50,55 @@ fn get_chars(selected_sets: &[&str]) -> Result<Vec<char>, String> {
return Err("Empty character pool".to_string()); return Err("Empty character pool".to_string());
} }
// Remove duplicates // Optimize for common case where no duplicates exist
let mut unique_chars = Vec::new(); if selected_sets.len() == 1 ||
let mut seen = HashSet::new(); (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());
for ch in all_chars { for ch in all_chars {
if seen.insert(ch) { if seen.insert(ch) {
unique_chars.push(ch); unique_chars.push(ch);
} }
} }
unique_chars.shrink_to_fit();
Ok(unique_chars) Ok(unique_chars)
} }
#[inline]
fn generate_random_string(chars: &[char], length: usize) -> String { fn generate_random_string(chars: &[char], length: usize) -> String {
let mut rng = OsRng; let mut rng = OsRng;
(0..length) let mut result = String::with_capacity(length);
.map(|_| *chars.choose(&mut rng).unwrap())
.collect() // 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
} }
fn main() { fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
if args.len() < 2 || args.contains(&"-h".to_string()) || args.contains(&"--help".to_string()) { if args.len() < 2 || args.contains(&"-h".to_string()) {
print_help(); print_help();
return; return;
} }
@ -80,21 +110,22 @@ fn main() {
let mut i = 1; let mut i = 1;
while i < args.len() { while i < args.len() {
match args[i].as_str() { match args[i].as_str() {
"-l" | "--length" => { "-l" => {
if i + 1 < args.len() { if i + 1 < args.len() {
if let Ok(n) = args[i + 1].parse::<usize>() { match args[i + 1].parse::<usize>() {
length = n; Ok(n) => length = n,
} else { Err(_) => {
eprintln!("Error: Invalid length"); eprintln!("Error: Invalid length");
process::exit(1); process::exit(1);
} }
}
i += 2; i += 2;
} else { } else {
eprintln!("Error: Missing length value"); eprintln!("Error: Missing length value");
process::exit(1); process::exit(1);
} }
} }
"-s" | "--sets" => { "-s" => {
if i + 1 < args.len() { if i + 1 < args.len() {
sets = &args[i + 1]; sets = &args[i + 1];
i += 2; i += 2;
@ -103,27 +134,29 @@ fn main() {
process::exit(1); process::exit(1);
} }
} }
"-c" | "--count" => { "-c" => {
if i + 1 < args.len() { if i + 1 < args.len() {
if let Ok(n) = args[i + 1].parse::<usize>() { match args[i + 1].parse::<usize>() {
count = n; Ok(n) => count = n,
} else { Err(_) => {
eprintln!("Error: Invalid count"); eprintln!("Error: Invalid count");
process::exit(1); process::exit(1);
} }
}
i += 2; i += 2;
} else { } else {
eprintln!("Error: Missing count value"); eprintln!("Error: Missing count value");
process::exit(1); process::exit(1);
} }
} }
arg if i == 1 && !arg.starts_with("-") => { arg if i == 1 && !arg.starts_with('-') => {
if let Ok(n) = arg.parse::<usize>() { match arg.parse::<usize>() {
length = n; Ok(n) => length = n,
} else { Err(_) => {
eprintln!("Error: Invalid length"); eprintln!("Error: Invalid length");
process::exit(1); process::exit(1);
} }
}
i += 1; i += 1;
} }
_ => { _ => {