Loading page…
Rust walkthroughs
Loading page…
rand::distributions::Alphanumeric generate secure random strings for tokens?rand::distributions::Alphanumeric is a distribution that samples uniformly from the characters a-z, A-Z, and 0-9 (62 characters total), providing a convenient way to generate random strings suitable for tokens, identifiers, and other use cases where alphanumeric characters are required. When combined with a cryptographic random number generator like rand::rngs::ThreadRng or OsRng, it produces strings that are suitable for security-sensitive applications. The distribution ensures each character has equal probability of being selected, making the output suitable for tokens where predictability would be a security concern.
use rand::Rng;
use rand::distributions::Alphanumeric;
fn main() {
let mut rng = rand::thread_rng();
// Generate a random string of 16 characters
let random_string: String = (0..16)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
println!("Random string: {}", random_string);
// Example output: aB3xK9mP2qR7sT1n
}Alphanumeric samples from 62 characters: 26 lowercase, 26 uppercase, and 10 digits.
use rand::distributions::Alphanumeric;
use std::char;
fn main() {
// Alphanumeric includes:
// - a-z (26 characters)
// - A-Z (26 characters)
// - 0-9 (10 characters)
// Total: 62 characters
let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.chars()
.collect();
println!("Character set: {:?}", chars);
println!("Total characters: {}", chars.len());
// Each character has equal probability: 1/62 ≈ 1.61%
// This provides uniform entropy per character
}The uniform distribution ensures each character contributes equally to randomness.
use rand::Rng;
use rand::distributions::Alphanumeric;
fn generate_token(length: usize) -> String {
let mut rng = rand::thread_rng();
(0..length)
.map(|_| rng.sample(Alphanumeric) as char)
.collect()
}
fn main() {
let api_key = generate_token(32);
let session_id = generate_token(24);
let reset_code = generate_token(8);
println!("API key: {}", api_key);
println!("Session ID: {}", session_id);
println!("Reset code: {}", reset_code);
}Thread-local RNG is cryptographically secure by default in the rand crate.
use rand::Rng;
use rand::rngs::OsRng;
use rand::distributions::Alphanumeric;
fn main() {
// thread_rng() uses OsRng internally, which is cryptographically secure
let mut rng = rand::thread_rng();
// For explicit cryptographic security:
let mut os_rng = OsRng;
let token_crypto: String = (0..32)
.map(|_| os_rng.sample(Alphanumeric) as char)
.collect();
println!("Cryptographically secure token: {}", token_crypto);
// Security properties:
// - Each character is uniformly random
// - Characters are independent
// - No predictable patterns
// - Suitable for security tokens
}OsRng provides cryptographically secure randomness from the operating system.
use rand::Rng;
use rand::distributions::Alphanumeric;
fn calculate_entropy(length: usize) -> f64 {
// Alphanumeric has 62 characters
// Entropy per character = log2(62) ≈ 5.95 bits
let entropy_per_char = (62_f64).log2();
length as f64 * entropy_per_char
}
fn main() {
let lengths = [8, 16, 24, 32, 64];
for len in lengths {
let entropy = calculate_entropy(len);
println!("{} chars = {:.2} bits of entropy", len, entropy);
// Example output:
// 8 chars = 47.63 bits
// 16 chars = 95.26 bits
// 24 chars = 142.89 bits
// 32 chars = 190.52 bits
// 64 chars = 381.05 bits
}
// For context:
// - 128 bits is considered secure for most applications
// - 22 alphanumeric characters ≈ 128 bits
}Each character contributes ~5.95 bits of entropy (log₂(62)).
use rand::Rng;
use rand::distributions::Alphanumeric;
fn generate_url_safe_token(length: usize) -> String {
// Alphanumeric characters are URL-safe
// No special characters that need encoding
let mut rng = rand::thread_rng();
(0..length)
.map(|_| rng.sample(Alphanumeric) as char)
.collect()
}
fn main() {
let token = generate_url_safe_token(32);
// Safe to use in:
// - URLs: https://example.com/verify/{token}
// - Query params: ?token={token}
// - Headers: Authorization: Bearer {token}
// - File names (no special characters)
println!("URL-safe token: {}", token);
println!("Safe for URL: https://example.com/reset/{}", token);
}Alphanumeric tokens don't require URL encoding.
use rand::Rng;
use rand::distributions::{Alphanumeric, DistString, Standard};
fn main() {
let mut rng = rand::thread_rng();
// Alphanumeric: a-z, A-Z, 0-9 (62 characters)
let alphanumeric: String = Alphanumeric.sample_string(&mut rng, 16);
println!("Alphanumeric: {}", alphanumeric);
// For comparison, other common distributions:
// Hexadecimal: 0-9, a-f (16 characters)
// - Less entropy per character: log2(16) = 4 bits
// - Needs more characters for same security
// Base64: a-z, A-Z, 0-9, +, / (64 characters)
// - Similar entropy: log2(64) = 6 bits
// - Includes + and / which aren't URL-safe
// Standard printable: all printable ASCII
// - More characters but includes special chars
}Alphanumeric balances entropy density with URL safety.
use rand::distributions::{Alphanumeric, DistString};
use rand::rngs::OsRng;
fn main() {
let mut rng = OsRng;
// DistString provides a convenient method
let token = Alphanumeric.sample_string(&mut rng, 32);
println!("Token: {}", token);
// This is equivalent to:
let token2: String = (0..32)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
// sample_string is more readable and potentially optimized
}DistString::sample_string provides a convenient shorthand.
use rand::Rng;
use rand::distributions::Alphanumeric;
fn collision_probability(token_length: usize, num_tokens: u64) -> f64 {
// Birthday problem approximation
// 62 characters, each position has 62 possibilities
let space = 62_f64.powi(token_length as i32);
let n = num_tokens as f64;
// Approximate collision probability
// P ≈ n² / (2 * space)
let prob = (n * n) / (2.0 * space);
prob.min(1.0)
}
fn main() {
// For 16-character tokens
let length = 16;
for count in [1_000, 10_000, 100_000, 1_000_000] {
let prob = collision_probability(length, count);
println!("{} tokens of {} chars: collision prob = {:.2e}",
count, length, prob);
}
// With 62^16 ≈ 4.8 × 10^28 possible tokens
// Collision probability is extremely low for practical numbers
}Longer tokens reduce collision probability exponentially.
use rand::Rng;
use rand::distributions::Alphanumeric;
use std::time::Instant;
fn main() {
let mut rng = rand::thread_rng();
// Generate many tokens and measure
let start = Instant::now();
let iterations = 100_000;
for _ in 0..iterations {
let _token: String = (0..32)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
}
let duration = start.elapsed();
let per_token = duration / iterations;
println!("Time per 32-char token: {:?}", per_token);
// Alphanumeric sampling is efficient:
// - Simple modulo operation for uniform sampling
// - No rejection sampling needed (62 divides evenly)
// - Character mapping is trivial
}Alphanumeric sampling is efficient with uniform distribution.
use rand::Rng;
use rand::rngs::OsRng;
use rand::distributions::Alphanumeric;
#[derive(Debug)]
pub struct SecureToken {
value: String,
}
impl SecureToken {
pub fn new(length: usize) -> Self {
let mut rng = OsRng;
let value = (0..length)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
Self { value }
}
pub fn as_str(&self) -> &str {
&self.value
}
pub fn into_string(self) -> String {
self.value
}
pub fn entropy_bits(&self) -> f64 {
self.value.len() as f64 * (62_f64).log2()
}
}
fn main() {
let token = SecureToken::new(32);
println!("Token: {}", token.as_str());
println!("Length: {} characters", token.as_str().len());
println!("Entropy: {:.2} bits", token.entropy_bits());
}Encapsulate token generation in a type for consistent security practices.
use rand::Rng;
use rand::distributions::Alphanumeric;
struct TokenConfig {
length: usize,
prefix: Option<String>,
}
fn generate_token_with_config(config: TokenConfig) -> String {
let mut rng = rand::thread_rng();
let random_part: String = (0..config.length)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
match config.prefix {
Some(ref prefix) => format!("{}_{}", prefix, random_part),
None => random_part,
}
}
fn main() {
// API key with prefix
let api_key = generate_token_with_config(TokenConfig {
length: 24,
prefix: Some("sk".to_string()),
});
// Session ID without prefix
let session = generate_token_with_config(TokenConfig {
length: 32,
prefix: None,
});
println!("API key: {}", api_key);
println!("Session: {}", session);
}Add prefixes to identify token types while keeping the random portion.
use rand::Rng;
use rand::rngs::SmallRng;
use rand::distributions::Alphanumeric;
use rand::SeedableRng;
fn main() {
// SmallRng is FAST but NOT cryptographically secure
// Only use for non-security purposes
let mut small_rng = SmallRng::seed_from_u64(42);
let predictable_token: String = (0..16)
.map(|_| small_rng.sample(Alphanumeric) as char)
.collect();
println!("Predictable token (seeded): {}", predictable_token);
// With same seed, produces same token:
let mut rng2 = SmallRng::seed_from_u64(42);
let same_token: String = (0..16)
.map(|_| rng2.sample(Alphanumeric) as char)
.collect();
println!("Same seed = same token: {}", same_token);
// WARNING: Do NOT use SmallRng for:
// - Password reset tokens
// - Session IDs
// - API keys
// - Nonce values
// - Any security-sensitive tokens
}Use SmallRng only for testing or non-security applications.
use rand::Rng;
use rand::distributions::Alphanumeric;
use std::collections::HashSet;
fn main() {
let mut rng = rand::thread_rng();
// Store tokens securely
let mut issued_tokens: HashSet<String> = HashSet::new();
// Issue tokens
for _ in 0..10 {
let token: String = (0..16)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
issued_tokens.insert(token);
}
println!("Issued {} tokens", issued_tokens.len());
// Verify token (constant-time comparison would be better)
let some_token = issued_tokens.iter().next().unwrap();
let is_valid = issued_tokens.contains(some_token);
println!("Token valid: {}", is_valid);
// For production:
// - Use constant-time comparison
// - Consider hashing tokens before storage
// - Implement expiration
}Consider hashing tokens before storage for additional security.
use rand::Rng;
use rand::distributions::Alphanumeric;
fn main() {
let mut rng = rand::thread_rng();
// Alphanumeric: 62 characters, ~5.95 bits/char
let alphanum: String = (0..22)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
// For 128-bit entropy:
// - Alphanumeric: ceil(128/5.95) = 22 characters
// - Hex: 128/4 = 32 characters
println!("Alphanumeric (22 chars): {}", alphanum);
// Alphanumeric advantages:
// - More entropy per character
// - Shorter tokens for same security
// - Still URL-safe
// Hex advantages:
// - Simpler character set
// - Easy to parse back to bytes
// - More widely supported
}Alphanumeric is more compact than hex for equivalent entropy.
use rand::Rng;
use rand::distributions::DistString;
use rand::seq::SliceRandom;
fn generate_custom_token(length: usize) -> String {
let mut rng = rand::thread_rng();
// Custom character set
let chars = "abcdefghijkmnpqrstuvwxyz23456789";
// Removed: l, 1, I, 0, O (avoid confusion)
(0..length)
.map(|_| chars.chars().choose(&mut rng).unwrap())
.collect()
}
fn main() {
let token = generate_custom_token(16);
println!("Custom charset token: {}", token);
// Use Alphanumeric when you want all characters
// Use custom sets when you need to exclude similar characters
}Custom character sets can exclude visually similar characters.
use rand::Rng;
use rand::distributions::Alphanumeric;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
struct TimedToken {
value: String,
expires_at: Instant,
}
impl TimedToken {
fn new(length: usize, ttl: Duration) -> Self {
let mut rng = rand::thread_rng();
let value = (0..length)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
Self {
value,
expires_at: Instant::now() + ttl,
}
}
fn is_valid(&self) -> bool {
Instant::now() < self.expires_at
}
fn value(&self) -> &str {
&self.value
}
}
fn main() {
// Token valid for 1 hour
let token = TimedToken::new(32, Duration::from_secs(3600));
println!("Token: {}", token.value());
println!("Valid: {}", token.is_valid());
}Add expiration to tokens for time-limited validity.
use rand::Rng;
use rand::distributions::Alphanumeric;
fn is_valid_alphanumeric(s: &str) -> bool {
s.chars().all(|c| c.is_ascii_alphanumeric())
}
fn main() {
let mut rng = rand::thread_rng();
// Generate and validate
let token: String = (0..24)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
assert!(is_valid_alphanumeric(&token));
// Use cases suitable for Alphanumeric tokens:
// 1. Password reset tokens
let reset_token: String = (0..32)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
// 2. Email verification codes
let verify_code: String = (0..16)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
// 3. Session identifiers
let session_id: String = (0..24)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
// 4. API keys
let api_key: String = (0..48)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
// 5. Unique identifiers
let unique_id: String = (0..12)
.map(|_| rng.sample(Alphanumeric) as char)
.collect();
println!("Reset token: {}", reset_token);
println!("Verify code: {}", verify_code);
println!("Session ID: {}", session_id);
}Alphanumeric tokens suit many authentication and identification use cases.
Alphanumeric characteristics:
| Property | Value | |----------|-------| | Character set | a-z, A-Z, 0-9 (62 chars) | | Entropy per char | ~5.95 bits (log₂(62)) | | URL-safe | Yes | | File-name safe | Yes | | Case-sensitive | Yes |
Security properties with cryptographic RNG:
| Property | Detail | |----------|--------| | Uniform distribution | Each char has equal probability | | Independence | Each character is independent | | Unpredictability | No patterns or correlations | | Collision resistance | Very low probability for sufficient length |
Token length recommendations:
| Use case | Recommended length | Entropy | |----------|------------------|---------| | Short-lived codes | 8-12 chars | ~48-72 bits | | Session IDs | 16-24 chars | ~95-143 bits | | Password reset | 32 chars | ~190 bits | | API keys | 32-64 chars | ~190-381 bits |
RNG selection:
| RNG | Use case |
|-----|----------|
| thread_rng() | General security-sensitive |
| OsRng | Explicit cryptographic security |
| SmallRng | Testing, non-security only |
Key insight: rand::distributions::Alphanumeric provides a uniform distribution over 62 characters (a-z, A-Z, 0-9), making it ideal for generating URL-safe tokens with good entropy density. When combined with a cryptographically secure random number generator like thread_rng() or OsRng, it produces tokens suitable for security-sensitive applications. Each character contributes approximately 5.95 bits of entropy (log₂(62)), so a 22-character token provides roughly 128 bits of entropy—suitable for most security applications. The alphanumeric character set is URL-safe without requiring encoding, making these tokens convenient for use in URLs, headers, and file names. For security-critical tokens, always use thread_rng() or OsRng rather than predictable generators like SmallRng. The distribution's uniform sampling ensures each character has equal probability, eliminating biases that could make tokens predictable.