File Coverage

blib/lib/Data/Password/zxcvbn/Match/Sequence.pm
Criterion Covered Total %
statement 39 39 100.0
branch 18 18 100.0
condition 5 5 100.0
subroutine 6 6 100.0
pod 4 4 100.0
total 72 72 100.0


line stmt bran cond sub pod time code
1             package Data::Password::zxcvbn::Match::Sequence;
2 3     3   8864 use Moo;
  3         8  
  3         19  
3             with 'Data::Password::zxcvbn::Match';
4             our $VERSION = '1.1.1'; # VERSION
5             # ABSTRACT: match class for sequences of uniformly-spaced codepoints
6              
7              
8             has ascending => (is => 'ro', default => 1);
9              
10              
11             sub estimate_guesses {
12 654     654 1 15554 my ($self,$min_guesses) = @_;
13              
14 654         2420 my $first_char = substr($self->token,0,1);
15              
16 654         1303 my $guesses;
17             # lower guesses for obvious starting points
18 654 100       3909 if ($first_char =~ m{[aAzZ019]}) {
    100          
19 179         380 $guesses = 4;
20             }
21             elsif ($first_char =~ m{[0-9]}) {
22 180         451 $guesses = 10; # digits
23             }
24             else {
25             # could give a higher base for uppercase, assigning 26 to both
26             # upper and lower sequences is more conservative.
27 295         800 $guesses = 26;
28             }
29              
30 654 100       3031 $guesses *= 2 unless $self->ascending;
31              
32 654         3194 return $guesses * length($self->token);
33             }
34              
35              
36             sub feedback_warning {
37 5     5 1 19 my ($self) = @_;
38              
39 5         31 return 'Sequences like abc or 6543 are easy to guess';
40             }
41              
42             sub feedback_suggestions {
43 5     5 1 30 return [ 'Avoid sequences' ];
44             }
45              
46              
47             my $MAX_DELTA = 5;
48              
49             sub make {
50 1506     1506 1 94681 my ($class, $password) = @_;
51             # Identifies sequences by looking for repeated differences in
52             # unicode codepoint. this allows skipping, such as 9753, and also
53             # matches some extended unicode sequences such as Greek and
54             # Cyrillic alphabets.
55             #
56             # for example, consider the input 'abcdb975zy'
57             #
58             # password: a b c d b 9 7 5 z y
59             # index: 0 1 2 3 4 5 6 7 8 9
60             # delta: 1 1 1 -2 -41 -2 -2 69 1
61             #
62             # expected result:
63             # [(i, j, delta), ...] = [(0, 3, 1), (5, 7, -2), (8, 9, 1)]
64              
65 1506         4024 my $length = length($password);
66 1506 100       5802 return [] if $length <= 1;
67              
68 1055         2409 my @matches;
69              
70             my $update = sub {
71 6378     6378   13499 my ($i,$j,$delta) = @_;
72 6378   100     15858 my $abs_delta = abs($delta||0);
73 6378 100 100     23267 return unless $j-$i>1 or $abs_delta == 1;
74 733 100       3368 return if $abs_delta == 0;
75 690 100       2491 return if $abs_delta > $MAX_DELTA;
76              
77 662         2379 my $token = substr($password,$i,$j-$i+1);
78 662         18280 push @matches, $class->new({
79             token => $token,
80             i => $i, j => $j,
81             ascending => !!($delta>0),
82             });
83 1055         9696 };
84              
85 1055         2360 my $i=0;
86 1055         2504 my $last_delta;
87 1055         3868 for my $k (1..$length-1) {
88 6611         14967 my $delta = ord(substr($password,$k,1)) - ord(substr($password,$k-1,1));
89 6611 100       13644 $last_delta = $delta unless defined($last_delta);
90 6611 100       13631 next if $delta == $last_delta;
91 5323         9300 my $j = $k-1;
92 5323         12042 $update->($i,$j,$last_delta);
93 5323         28016 $i = $j; $last_delta = $delta;
  5323         10422  
94             }
95 1055         5350 $update->($i,$length-1,$last_delta);
96              
97 1055         18942 return \@matches;
98             }
99              
100              
101             around fields_for_json => sub {
102             my ($orig,$self) = @_;
103             ( $self->$orig(), qw(ascending) )
104             };
105              
106             1;
107              
108             __END__
109              
110             =pod
111              
112             =encoding UTF-8
113              
114             =head1 NAME
115              
116             Data::Password::zxcvbn::Match::Sequence - match class for sequences of uniformly-spaced codepoints
117              
118             =head1 VERSION
119              
120             version 1.1.1
121              
122             =head1 DESCRIPTION
123              
124             This class represents the guess that a certain substring of a
125             password, consisting of uniformly-spaced codepoints, is easy to guess.
126              
127             =head1 ATTRIBUTES
128              
129             =head2 C<ascending>
130              
131             Boolean, true if the sequence starts at a lower codepoint and ends at
132             a higher one (e.g. C<acegi> is ascending, C<86420> is not).
133              
134             =head1 METHODS
135              
136             =head2 C<estimate_guesses>
137              
138             The number of guesses is I<linear> with the length of the
139             sequence. Descending sequences get a higher estimate, sequences that
140             start at obvious points (e.g. C<A> or C<1>) get lower estimates.
141              
142             =head2 C<feedback_warning>
143              
144             =head2 C<feedback_suggestions>
145              
146             This class suggests not using sequences.
147              
148             =head2 C<make>
149              
150             my @matches = @{ Data::Password::zxcvbn::Match::Sequence->make(
151             $password,
152             ) };
153              
154             Scans the C<$password> for sequences of characters whose codepoints
155             increase or decrease by a constant.
156              
157             =head2 C<fields_for_json>
158              
159             The JSON serialisation for matches of this class will contain C<token
160             i j guesses guesses_log10 ascending>.
161              
162             =head1 AUTHOR
163              
164             Gianni Ceccarelli <gianni.ceccarelli@broadbean.com>
165              
166             =head1 COPYRIGHT AND LICENSE
167              
168             This software is copyright (c) 2022 by BroadBean UK, a CareerBuilder Company.
169              
170             This is free software; you can redistribute it and/or modify it under
171             the same terms as the Perl 5 programming language system itself.
172              
173             =cut