Created
December 23, 2025 18:23
-
-
Save hgaiser/49b0f8150f932e2955500671dd017ef4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::io::{self, Read}; | |
| use std::env; | |
| // Define the character set to use for the password. | |
| // You can customize this string to include or exclude specific characters. | |
| const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ | |
| abcdefghijklmnopqrstuvwxyz\ | |
| 0123456789\ | |
| !@#$%^&*()_+-=[]{}|;:,.<>?"; | |
| /// Fills the provided buffer with cryptographically secure random bytes. | |
| #[cfg(unix)] | |
| fn get_random_bytes(buffer: &mut [u8]) -> io::Result<()> { | |
| // On Unix-like systems, we read from /dev/urandom. | |
| // /dev/urandom is non-blocking and suitable for generating cryptographic keys. | |
| let mut file = std::fs::File::open("/dev/urandom")?; | |
| file.read_exact(buffer)?; | |
| Ok(()) | |
| } | |
| /// Fills the provided buffer with cryptographically secure random bytes. | |
| #[cfg(windows)] | |
| mod win_api { | |
| use std::os::raw::c_void; | |
| // Define the constant for the system-preferred RNG. | |
| // This allows us to pass NULL as the algorithm handle. | |
| pub const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; | |
| // Link to the bcrypt.dll system library. | |
| #[link(name = "bcrypt")] | |
| extern "system" { | |
| // BCryptGenRandom function signature. | |
| // Documentation: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom | |
| fn BCryptGenRandom( | |
| halgorithm: *mut c_void, | |
| pbbuffer: *mut u8, | |
| cbbuffer: u32, | |
| dwflags: u32, | |
| ) -> i32; // NTSTATUS (0 indicates success) | |
| } | |
| } | |
| #[cfg(windows)] | |
| fn get_random_bytes(buffer: &mut [u8]) -> io::Result<()> { | |
| // Safety: We are calling an external Windows API. | |
| // We verify that the buffer length fits into a u32. | |
| let buffer_len = buffer.len(); | |
| if buffer_len > u32::MAX as usize { | |
| return Err(io::Error::new(io::ErrorKind::InvalidInput, "Buffer too large")); | |
| } | |
| let status = unsafe { | |
| win_api::BCryptGenRandom( | |
| std::ptr::null_mut(), // hAlgorithm (NULL implies system preferred RNG) | |
| buffer.as_mut_ptr(), // pBuffer | |
| buffer_len as u32, // cbBuffer | |
| win_api::BCRYPT_USE_SYSTEM_PREFERRED_RNG, | |
| ) | |
| }; | |
| if status == 0 { | |
| Ok(()) | |
| } else { | |
| Err(io::Error::last_os_error()) | |
| } | |
| } | |
| /// Helper structure to buffer random bytes, reducing system call overhead. | |
| struct RandomBuffer { | |
| buffer: [u8; 64], | |
| position: usize, | |
| } | |
| impl RandomBuffer { | |
| fn new() -> Self { | |
| Self { | |
| buffer: [0u8; 64], | |
| position: 64, // Start with "empty" buffer to force fill on first use | |
| } | |
| } | |
| fn get_byte(&mut self) -> u8 { | |
| if self.position >= self.buffer.len() { | |
| // Refill buffer if empty | |
| get_random_bytes(&mut self.buffer).expect("Fatal: Failed to generate secure random bytes"); | |
| self.position = 0; | |
| } | |
| let byte = self.buffer[self.position]; | |
| self.position += 1; | |
| byte | |
| } | |
| } | |
| /// Generates a secure password of the specified length. | |
| fn generate_password(length: usize) -> String { | |
| let charset_len = CHARSET.len(); | |
| let mut rng = RandomBuffer::new(); | |
| let mut password = String::with_capacity(length); | |
| // To ensure a uniform distribution, we cannot simply do `random_byte % charset_len`. | |
| // Because 256 is not perfectly divisible by most charset lengths, using modulo | |
| // introduces "modulo bias" (some characters would appear slightly more often). | |
| // | |
| // We calculate the largest multiple of charset_len that fits in a u8 (0..256). | |
| // We reject random bytes that are greater than or equal to this value. | |
| let rejection_threshold = 256 - (256 % charset_len); | |
| while password.len() < length { | |
| let random_byte = rng.get_byte() as usize; | |
| if random_byte < rejection_threshold { | |
| // The byte is safe to map to a character index | |
| let index = random_byte % charset_len; | |
| let char = CHARSET[index] as char; | |
| password.push(char); | |
| } | |
| // If random_byte >= rejection_threshold, we discard it and try again (Rejection Sampling) | |
| } | |
| password | |
| } | |
| fn main() { | |
| let args: Vec<String> = env::args().collect(); | |
| // Default length if no argument is provided | |
| let length: usize = if args.len() > 1 { | |
| match args[1].parse() { | |
| Ok(l) if l > 0 => l, | |
| _ => { | |
| eprintln!("Please provide a valid positive integer for the password length."); | |
| return; | |
| } | |
| } | |
| } else { | |
| 16 | |
| }; | |
| let password = generate_password(length); | |
| println!("{}", password); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment