1#![deny(missing_docs)]
5
6use colored::Colorize;
7use zxcvbn::{Entropy, Match};
8
9macro_rules! info {
10 ($name:expr) => {
11 println!("{}", $name.bright_blue().bold());
12 };
13 ($name:expr, $value:expr) => {
14 println!("{} {}", $name.bright_blue().bold(), $value);
15 };
16}
17
18pub fn run(password: &str, hide_password: bool) -> Result<(), String> {
20 if password.is_empty() {
21 return Err("empty password".into());
22 }
23
24 let entropy = zxcvbn::zxcvbn(password, &[]).unwrap();
25
26 print!("{} ", "➜".magenta());
27 if hide_password {
28 print!("{}", "<hidden>".magenta());
29 } else {
30 print_password_tokenized(&entropy);
31 }
32 println!();
33
34 main_information(&entropy);
35 guesses(&entropy);
36 crack_time(&entropy);
37
38 if !hide_password {
39 info!("sequence");
40 sequence(entropy.sequence(), 0);
41 }
42
43 println!();
44 println!(
45 "{}",
46 format!(
47 "zxcvbn done in {} ms",
48 entropy.calculation_time().as_millis()
49 )
50 .bright_black()
51 );
52
53 Ok(())
54}
55
56fn print_password_tokenized(entropy: &Entropy) {
57 for (i, token) in entropy.sequence().iter().map(|m| &m.token).enumerate() {
58 print!(
59 "{}",
60 if (i + 1) % 2 == 0 {
61 token.on_bright_black()
62 } else {
63 token.normal()
64 }
65 );
66 }
67}
68
69fn main_information(entropy: &Entropy) {
70 info!(
71 "score",
72 format!(
73 "{}{}",
74 match entropy.score() {
75 0 => "0".bright_red(),
76 1 => "1".red(),
77 2 => "2".yellow(),
78 3 => "3".green(),
79 4 => "4".bright_green(),
80 _ => unreachable!(),
81 },
82 " / 4"
83 )
84 .bold()
85 );
86
87 if let Some(feedback) = entropy.feedback().clone() {
88 if let Some(warning) = feedback.warning() {
89 println!("{} {}", "warning".bright_yellow().bold(), warning);
90 }
91
92 if !feedback.suggestions().is_empty() {
93 println!("{}", "suggestions".bright_green().bold());
94
95 let i_last = feedback.suggestions().len() - 1;
96 for (i, suggestion) in feedback.suggestions().iter().enumerate() {
97 println!(
98 "{} {}",
99 if i < i_last { " ├" } else { " └" }.bright_green().bold(),
100 suggestion
101 );
102 }
103 }
104 }
105}
106
107fn guesses(entropy: &Entropy) {
108 info!(
109 "guesses",
110 "1e".to_string() + &entropy.guesses_log10().to_string()
111 );
112}
113
114macro_rules! fmt_crack_time {
115 ($entropy:expr, $name:ident, $desc:expr) => {
116 format!(
117 "{} {}",
118 $entropy.crack_times().$name().to_string(),
119 $desc.bright_black(),
120 )
121 };
122}
123
124fn crack_time(entropy: &Entropy) {
125 info!("crack time");
126 info!(
127 " ├ 10²/hour",
128 fmt_crack_time!(entropy, online_throttling_100_per_hour, "online, throttled")
129 );
130 info!(
131 " ├ 10 /sec",
132 fmt_crack_time!(
133 entropy,
134 online_no_throttling_10_per_second,
135 "online, not throttled"
136 )
137 );
138 info!(
139 " ├ 10⁴ /sec",
140 fmt_crack_time!(
141 entropy,
142 offline_slow_hashing_1e4_per_second,
143 "offline, slow hash"
144 )
145 );
146 info!(
147 " └ 10¹⁰/sec",
148 fmt_crack_time!(
149 entropy,
150 offline_fast_hashing_1e10_per_second,
151 "offline, fast hash"
152 )
153 );
154}
155
156macro_rules! pattern_info {
157 ($name:expr, $value:expr, $indent:expr) => {
158 println!(
159 " {}{} {} {}",
160 " ".repeat($indent),
161 "├".bright_blue(),
162 $name.bright_blue(),
163 $value
164 );
165 };
166 (last $name:expr, $value:expr, $indent:expr) => {
167 println!(
168 " {}{} {} {}",
169 " ".repeat($indent),
170 "└".bright_blue(),
171 $name.bright_blue(),
172 $value
173 );
174 };
175 (type $name:expr, $indent:expr) => {
176 println!(
177 " {}{} type {}",
178 " ".repeat($indent),
179 "├".bright_blue(),
180 $name.blue()
181 );
182 };
183 (type last $name:expr, $indent:expr) => {
184 println!(
185 " {}{} type {}",
186 " ".repeat($indent),
187 "└".bright_blue(),
188 $name.blue()
189 )
190 };
191}
192
193fn sequence(seq: &[Match], indent: usize) {
194 use zxcvbn::matching::patterns::MatchPattern::*;
195
196 for (i, part) in seq.iter().enumerate() {
197 println!(
198 " {}{}",
199 " ".repeat(indent),
200 if (i + 1) % 2 == 0 {
201 part.token.underline().on_bright_black()
202 } else {
203 part.token.underline()
204 }
205 );
206
207 match &part.pattern {
208 Dictionary(pattern) => {
209 pattern_info!(type "dictionary", indent);
210 pattern_info!("word", pattern.matched_word, indent);
211 pattern_info!("rank", pattern.rank, indent);
212 pattern_info!(
213 "dictionary",
214 format!("{:?}", pattern.dictionary_name),
215 indent
216 );
217 pattern_info!("reversed?", pattern.reversed, indent);
218 pattern_info!("l33t?", pattern.l33t, indent);
219 if let Some(substitutions) = &pattern.sub_display {
220 pattern_info!("substitutions", substitutions, indent);
221 }
222 pattern_info!("uppercase variations", pattern.uppercase_variations, indent);
223 pattern_info!("l33t variations", pattern.l33t_variations, indent);
224 pattern_info!(last "base guesses", pattern.base_guesses, indent);
225 }
226 Spatial(pattern) => {
227 pattern_info!(type "spatial", indent);
228 pattern_info!("graph", pattern.graph, indent);
229 pattern_info!("turns", pattern.turns, indent);
230 pattern_info!(last "shifts", pattern.shifted_count, indent);
231 }
232 Repeat(pattern) => {
233 pattern_info!(type "repeat", indent);
234 pattern_info!("base token", pattern.base_token, indent);
235 pattern_info!("base matches", "", indent);
236 sequence(&pattern.base_matches, indent + 2);
237 pattern_info!("base guesses", pattern.base_guesses, indent);
238 pattern_info!(last "repeat count", pattern.repeat_count, indent);
239 }
240 Sequence(pattern) => {
241 pattern_info!(type "sequence", indent);
242 pattern_info!("name", pattern.sequence_name, indent);
243 pattern_info!("space", pattern.sequence_space, indent);
244 pattern_info!(last "ascending?", pattern.ascending, indent);
245 }
246 Regex(pattern) => {
247 pattern_info!(type "regex", indent);
248 pattern_info!("name", pattern.regex_name, indent);
249 pattern_info!(last "match", format!("{:?}", pattern.regex_match), indent);
250 }
251 Date(pattern) => {
252 pattern_info!(type "date", indent);
253 pattern_info!("separator", pattern.separator, indent);
254 pattern_info!("year", pattern.year, indent);
255 pattern_info!("month", pattern.month, indent);
256 pattern_info!(last "day", pattern.day, indent);
257 }
258 BruteForce => pattern_info!(type last "bruteforce", indent),
259 }
260 }
261}