File Coverage

blib/lib/Data/Password/zxcvbn.pm
Criterion Covered Total %
statement 25 25 100.0
branch n/a
condition 6 10 60.0
subroutine 7 7 100.0
pod 1 1 100.0
total 39 43 90.7


line stmt bran cond sub pod time code
1             package Data::Password::zxcvbn;
2 1     1   748682 use strict;
  1         11  
  1         29  
3 1     1   6 use warnings;
  1         2  
  1         29  
4 1     1   6 use Module::Runtime qw(use_module);
  1         2  
  1         7  
5 1     1   542 use Data::Password::zxcvbn::MatchList;
  1         4  
  1         41  
6 1     1   8 use Data::Password::zxcvbn::TimeEstimate qw(estimate_attack_times);
  1         2  
  1         44  
7 1     1   6 use Exporter 'import';
  1         2  
  1         290  
8             our @EXPORT_OK=qw(password_strength);
9             our $VERSION = '1.1.0'; # VERSION
10             # ABSTRACT: Dropbox's password estimation logic
11              
12              
13             sub password_strength {
14 996     996 1 125990651 my ($password, $opts) = @_;
15              
16             my $match_list_module = $opts->{match_list_module}
17 996   50     8213 || 'Data::Password::zxcvbn::MatchList';
18             my $matches = use_module($match_list_module)->omnimatch(
19             $password, {
20             user_input => $opts->{user_input},
21             regexes => $opts->{regexes},
22             ranked_dictionaries => $opts->{ranked_dictionaries},
23             l33t_table => $opts->{l33t_table},
24             graphs => $opts->{graphs},
25             modules => $opts->{modules},
26             },
27 996         7923 );
28 996         39369 my $most_guessable = $matches->most_guessable_match_list();
29 996         61853 my $attack_times = estimate_attack_times($most_guessable->guesses);
30             my $feedback = $most_guessable->get_feedback(
31             $opts->{max_score_for_feedback},
32 996         6732 );
33              
34             return {
35             score => $most_guessable->score,
36             matches => $most_guessable->matches,
37             guesses => $most_guessable->guesses,
38             guesses_log10 => $most_guessable->guesses_log10,
39             feedback => {
40             warning => $feedback->{warning} || '',
41             suggestions => $feedback->{suggestions} || [],
42             },
43             crack_times_seconds => $attack_times->{crack_times_seconds} || {},
44             crack_times_display => $attack_times->{crack_times_display} || {},
45 996   100     5035 };
      50        
      50        
      50        
46             }
47              
48              
49             1;
50              
51             __END__
52              
53             =pod
54              
55             =encoding UTF-8
56              
57             =for :stopwords PBKDF2 scrypt bcrypt un
58              
59             =head1 NAME
60              
61             Data::Password::zxcvbn - Dropbox's password estimation logic
62              
63             =head1 VERSION
64              
65             version 1.1.0
66              
67             =head1 SYNOPSIS
68              
69             use Data::Password::zxcvbn qw(password_strength);
70              
71             my $strength = password_strength($my_password);
72             warn $strength->{warning} if $strength->{score} < 3;
73              
74             =head1 DESCRIPTION
75              
76             This is a Perl port of Dropbox's password strength estimation library,
77             L<< C<zxcvbn>|https://github.com/dropbox/zxcvbn >>.
78              
79             The code layout has been reworked to be generally nicer (e.g. we use
80             classes instead of dispatch tables, all data structures are immutable)
81             and to pre-compute more (e.g. the dictionaries are completely
82             pre-built, instead of being partially computed at run time).
83              
84             The code has been tested against the L<Python
85             port's|https://github.com/dwolfhub/zxcvbn-python>
86             F<password_expected_value.json> test. When the dictionaries contain
87             exactly the same data (including some words that are loaded wrongly by
88             the Javascript and Python code, due to escaping issues), our results
89             are identical. With the dictionaries as provided in this distribution,
90             the results (estimated number of guesses) are still within 1%.
91              
92             =head1 FUNCTIONS
93              
94             =head2 C<password_strength>
95              
96             my $strength = password_strength($password);
97              
98             This is the main entry point for the library, and the only function
99             you usually care about.
100              
101             It analyses the given string, finding the easiest way that a password
102             cracking algorithm would guess it, and reports on its findings.
103              
104             =head3 Return value
105              
106             The return value is a hashref, with these keys:
107              
108             =over 4
109              
110             =item *
111              
112             C<guesses>
113              
114             estimated guesses needed to crack password
115              
116             =item *
117              
118             C<guesses_log10>
119              
120             order of magnitude of C<guesses>
121              
122             =item *
123              
124             C<crack_times_seconds>
125              
126             hashref of back-of-the-envelope crack time estimations, in seconds,
127             based on a few scenarios:
128              
129             =over 4
130              
131             =item *
132              
133             C<online_throttling_100_per_hour>
134              
135             online attack on a service that rate-limits authentication attempts
136              
137             =item *
138              
139             C<online_no_throttling_10_per_second>
140              
141             online attack on a service that doesn't rate-limit, or where an
142             attacker has outsmarted rate-limiting.
143              
144             =item *
145              
146             C<offline_slow_hashing_1e4_per_second>
147              
148             offline attack. assumes multiple attackers, proper user-unique
149             salting, and a slow hash function with moderate work factor, such as
150             bcrypt, scrypt, PBKDF2.
151              
152             =item *
153              
154             C<offline_fast_hashing_1e10_per_second>
155              
156             offline attack with user-unique salting but a fast hash function like
157             SHA-1, SHA-256 or MD5. A wide range of reasonable numbers anywhere
158             from one billion - one trillion guesses per second, depending on
159             number of cores and machines; ball-parking at 10B/sec.
160              
161             =back
162              
163             =item *
164              
165             C<crack_times_display>
166              
167             same keys as C<crack_times_seconds>, but more useful for display: the
168             values are arrayrefs C<["english string",$value]> that can be passed
169             to I18N libraries like L<< C<Locale::Maketext> >> to get localised
170             versions with proper plurals
171              
172             =item *
173              
174             C<score>
175              
176             Integer from 0-4 (useful for implementing a strength bar):
177              
178             =over 4
179              
180             =item *
181              
182             C<0>
183              
184             too guessable: risky password. (C<< guesses < 10e3 >>)
185              
186             =item *
187              
188             C<1>
189              
190             very guessable: protection from throttled online attacks. (C<< guesses
191             < 10e6 >>)
192              
193             =item *
194              
195             C<2>
196              
197             somewhat guessable: protection from un-throttled online attacks. (C<<
198             guesses < 10e8 >>)
199              
200             =item *
201              
202             C<3>
203              
204             safely un-guessable: moderate protection from offline slow-hash
205             scenario. (C<< guesses < 10e10 >>)
206              
207             =item *
208              
209             C<4>
210              
211             very un-guessable: strong protection from offline slow-hash
212             scenario. (C<< guesses >= 10e10 >>)
213              
214             =back
215              
216             =item *
217              
218             C<feedback>
219              
220             hashref, verbal feedback to help choose better passwords, contains
221             useful information when C<< score <= 2 >>:
222              
223             =over 4
224              
225             =item *
226              
227             C<warning>
228              
229             a string (sometimes empty), or an arrayref C<[$string,@values]>
230             suitable for localisation. Explains what's wrong, e.g. 'this is a
231             top-10 common password'.
232              
233             =item *
234              
235             C<suggestions>
236              
237             a possibly-empty array of suggestions to help choose a less guessable
238             password. e.g. 'Add another word or two'; again, elements can be
239             strings or arrayrefs for localisation.
240              
241             =back
242              
243             =item *
244              
245             C<matches>
246              
247             the list of patterns that zxcvbn based the guess calculation on; this
248             is rarely useful to show to users
249              
250             =back
251              
252             All the objects in the returned value can be serialised to JSON, if
253             you set C<convert_blessed> or equivalent in your JSON library.
254              
255             =head3 Options
256              
257             my $strength = password_strength($password,\%options);
258              
259             You can pass in several options to customise the behaviour of this
260             function. From most-frequently useful:
261              
262             =over 4
263              
264             =item *
265              
266             C<user_input>
267              
268             the most useful option: a hashref of field names and values that
269             should be considered "obvious guesses", e.g. account name, user's real
270             name, company name, &c. (see L<<
271             C<Data::Password::zxcvbn::Match::UserInput> >>)
272              
273             =item *
274              
275             C<max_score_for_feedback>
276              
277             the maximum L<< /C<score> >> above which no feedback will be provided,
278             defaults to 2; provide a higher value if you want feedback even on
279             strong passwords
280              
281             =item *
282              
283             C<modules>
284              
285             arrayref of module names to use instead of the built-in
286             C<Data::Password::zxcvbn::Match::*> classes; if you want to I<add> a
287             module, you still have to list all the built-ins in this array; L<<
288             C<Data::Password::zxcvbn::Match::BruteForce> >> is special, and if
289             included here, it will be ignored
290              
291             =item *
292              
293             C<match_list_module>
294              
295             module name to use instead of L<< C<Data::Password::zxcvbn::MatchList>
296             >> to run all the computations; the module should really be a subclass
297             of that default one, with maybe some customised messages
298              
299             =item *
300              
301             C<ranked_dictionaries>
302              
303             =item *
304              
305             C<l33t_table>
306              
307             dictionaries and transliteration table, see L<<
308             C<Data::Password::zxcvbn::Match::Dictionary> >>
309              
310             =item *
311              
312             C<graphs>
313              
314             adjacency graphs for keyboard-related spatial guesses, see L<<
315             C<Data::Password::zxcvbn::Match::Spatial> >>
316              
317             =item *
318              
319             C<regexes>
320              
321             which regexes to use, see L<< C<Data::Password::zxcvbn::Match::Regex>
322             >>
323              
324             =back
325              
326             =head1 SEE ALSO
327              
328             =over
329              
330             =item *
331              
332             L<the original implementation by Dropbox|https://github.com/dropbox/zxcvbn>
333              
334             =item *
335              
336             L<the Python port|https://github.com/dwolfhub/zxcvbn-python>
337              
338             =back
339              
340             =head1 AUTHOR
341              
342             Gianni Ceccarelli <gianni.ceccarelli@broadbean.com>
343              
344             =head1 COPYRIGHT AND LICENSE
345              
346             This software is copyright (c) 2022 by BroadBean UK, a CareerBuilder Company.
347              
348             This is free software; you can redistribute it and/or modify it under
349             the same terms as the Perl 5 programming language system itself.
350              
351             =cut