passgen/random.rs
1//! # Randomness generation
2//!
3//! This module contains various sources of randomness. Randomness itself is handled by the `rand`
4//! crate. These types implement the [RandomSource] trait to generate *n* independent streams of
5//! randomness.
6//!
7//! Fundamentally, there are two types of randomness:
8//! * *Non-deterministic*, typically using whatever strong randomness generator is available on the
9//! system. This is the default for Passgen, and should be used when you want to generate a new
10//! random passphrase and do not need the ability to later re-generate it.
11//! * *Deterministic*. These are used for generating passphrases from some known secret using a
12//! pseudorandom number generator (such as a stream cipher). These should be used when you want
13//! to be able to later re-generate the same passphrase from the secret.
14//!
15//! Some of the randomness sources (specifically the [Zero] and [Xoshiro] ones) exist mainly for
16//! testing purposes and should not be used by end-users. They may be gated by some feature in the
17//! future to remove them from the Passgen tool.
18//!
19//! For each source of randomness, Passgen requires the ability to create *n* random number
20//! generators with independent streams of random values to allow it to generate passphrases
21//! from multiple threads, see the [RandomSource] documentation for more information on this.
22//!
23//! ## Examples
24//!
25//! Creating a randomness source, instantiating multiple random number generators from it
26//! and showing the the streams are independent and deterministic.
27//!
28//! ```
29//! use passgen::random::*;
30//! use rand::Rng;
31//!
32//! // using the xoshiro deterministic pseudorandom number generator
33//! let random = Xoshiro::new(564);
34//!
35//! // stream zero
36//! let mut rng = random.get_rng(0);
37//! assert_eq!(rng.gen::<u8>(), 45);
38//! assert_eq!(rng.gen::<u8>(), 106);
39//!
40//! // stream one (independent from zero)
41//! let mut rng = random.get_rng(1);
42//! assert_eq!(rng.gen::<u8>(), 156);
43//! assert_eq!(rng.gen::<u8>(), 212);
44//! ```
45
46use argon2::Argon2;
47use rand::RngCore;
48use rand::SeedableRng;
49use rand_chacha::ChaCha20Rng;
50use rand_xoshiro::Xoshiro256PlusPlus;
51use std::str::FromStr;
52
53/// Randomness source
54///
55/// Applications using Passgen should allow the user to configure the source of randomness by
56/// specifying this value. It defaults to using the [System] randomness generator, which is
57/// non-deterministic.
58#[derive(Debug, Clone, PartialEq)]
59pub enum Random {
60 System(System),
61 Zero(Zero),
62 Xoshiro(Xoshiro),
63 ChaCha20(ChaCha20),
64}
65
66impl Default for Random {
67 fn default() -> Self {
68 Self::System(System)
69 }
70}
71
72impl Random {
73 /// When using master pass mode, this method should be used (with the specified master
74 /// passphrase) to generate the appropriate source of randomness.
75 pub fn from_master_pass(pass: &str, salt: &str) -> Self {
76 let mut seed = [0; 32];
77 Argon2::new_with_secret(
78 b"passgen",
79 Default::default(),
80 Default::default(),
81 Default::default(),
82 )
83 .unwrap()
84 .hash_password_into(pass.as_bytes(), salt.as_bytes(), &mut seed)
85 .unwrap();
86 Self::ChaCha20(ChaCha20(seed))
87 }
88}
89
90/// Error parsing a [Random] value from a string.
91#[derive(thiserror::Error, Debug)]
92pub enum ParseError {
93 #[error("unknown randomness source '{0:}'")]
94 Unknown(String),
95}
96
97impl FromStr for Random {
98 type Err = ParseError;
99
100 fn from_str(input: &str) -> Result<Self, Self::Err> {
101 let split = input
102 .split_once(':')
103 .map(|(name, opt)| (name, Some(opt)))
104 .unwrap_or((input, None));
105 match split {
106 ("system", None) => Ok(Self::System(System)),
107 ("zero", None) => Ok(Self::Zero(Zero)),
108 ("xoshiro", Some(params)) => Ok(Self::Xoshiro(Xoshiro(params.parse().unwrap()))),
109 _ => Err(ParseError::Unknown(input.to_string())),
110 }
111 }
112}
113
114/// Source of randomness
115///
116/// ## Streams
117///
118/// When Passgen generates multiple random sequences, it could use a single stream of randomness
119/// sequentially, however this makes it impossible to use multi-threading. For that reason, every
120/// sequence it generates uses an independent stream of randomness. For nondeterministic
121/// randomness, these independent streams can actually be the same stream. For deterministic
122/// sources of randomness, these streams should be independent and each stream should always
123/// produce the same (pseudorandom) data.
124pub trait RandomSource {
125 /// Underlying type of the randomness source.
126 type Rng: RngCore + 'static;
127
128 /// Return a stream of randomness for the given index.
129 fn get_rng(&self, index: usize) -> Self::Rng;
130
131 /// Return a type-erased stream of randomness for the given index.
132 fn get_rng_boxed(&self, index: usize) -> Box<dyn RngCore> {
133 Box::new(self.get_rng(index))
134 }
135}
136
137impl RandomSource for Random {
138 type Rng = Box<dyn RngCore>;
139
140 fn get_rng(&self, index: usize) -> Box<dyn RngCore> {
141 match self {
142 Self::System(random) => random.get_rng_boxed(index),
143 Self::Zero(random) => random.get_rng_boxed(index),
144 Self::Xoshiro(random) => random.get_rng_boxed(index),
145 Self::ChaCha20(random) => random.get_rng_boxed(index),
146 }
147 }
148
149 fn get_rng_boxed(&self, index: usize) -> Box<dyn RngCore> {
150 self.get_rng(index)
151 }
152}
153
154/// System randomness generator for secure, non-deterministic passphrase generation.
155#[derive(Debug, Clone, Copy, PartialEq, Default)]
156pub struct System;
157
158impl RandomSource for System {
159 type Rng = rand::rngs::ThreadRng;
160
161 fn get_rng(&self, _index: usize) -> Self::Rng {
162 rand::thread_rng()
163 }
164}
165
166/// Source of randomness that always returns zeroes.
167#[derive(Debug, Clone, Copy, PartialEq, Default)]
168pub struct Zero;
169
170impl RandomSource for Zero {
171 type Rng = rand::rngs::mock::StepRng;
172
173 fn get_rng(&self, _index: usize) -> Self::Rng {
174 rand::rngs::mock::StepRng::new(0, 0)
175 }
176}
177
178/// [Xoshiro256PlusPlus] based pseudorandom generator.
179#[derive(Debug, Clone, Copy, PartialEq)]
180pub struct Xoshiro(u64);
181
182impl Xoshiro {
183 pub fn new(seed: u64) -> Self {
184 Xoshiro(seed)
185 }
186}
187
188impl RandomSource for Xoshiro {
189 type Rng = Xoshiro256PlusPlus;
190
191 fn get_rng(&self, index: usize) -> Self::Rng {
192 let mut rng = Xoshiro256PlusPlus::seed_from_u64(self.0);
193 for _ in 0..index {
194 rng.jump();
195 }
196 rng
197 }
198}
199
200/// [ChaCha20Rng]-based pseudorandom generator.
201#[derive(Debug, Clone, Copy, PartialEq)]
202pub struct ChaCha20([u8; 32]);
203
204impl RandomSource for ChaCha20 {
205 type Rng = ChaCha20Rng;
206
207 fn get_rng(&self, index: usize) -> Self::Rng {
208 let mut rng = ChaCha20Rng::from_seed(self.0);
209 rng.set_stream(index as u64);
210 rng
211 }
212}