Added hex, speed improvements, size reduction
This commit is contained in:
parent
ef7473a29e
commit
2558b97e0e
4 changed files with 104 additions and 44 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
51
README.md
51
README.md
|
|
@ -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` <number>: Length of each string (default: `16`)
|
- `LENGTH` <number>: 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
|
||||||
|
|
|
||||||
91
src/main.rs
91
src/main.rs
|
|
@ -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,13 +110,14 @@ 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 {
|
||||||
|
|
@ -94,7 +125,7 @@ fn main() {
|
||||||
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,13 +134,14 @@ 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 {
|
||||||
|
|
@ -117,12 +149,13 @@ fn main() {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue